1
0
Vsevolod Levitan 3 mēneši atpakaļ
vecāks
revīzija
7a358ba035

+ 34 - 121
AntColony/Algorithm/ACO.cs

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

+ 102 - 0
AntColony/Algorithm/DefaultAnt.cs

@@ -0,0 +1,102 @@
+
+namespace AntColony.Algorithm;
+
+/// <inheritdoc/>
+public class DefaultAnt : IAnt
+{
+    private readonly double alpha = 1;
+    private readonly double beta = 1;
+    private readonly double Q = 100;
+
+    private List<int> tour = [];
+    private double distance = double.PositiveInfinity;
+
+    /// <inheritdoc/>
+    public (List<int> tour, double distance)? ConstructTour(ref double[][] distances, ref double[][] pheromones)
+    {
+        tour = [];
+        distance = double.PositiveInfinity;
+        var visited = new bool[distances.Length];
+        var curCity = Random.Shared.Next(distances.Length);
+
+        tour.Add(curCity);
+        visited[curCity] = true;
+
+        while (tour.Count < distances.Length)
+        {
+            var sel = SelectNextCity(curCity, ref visited, ref distances, ref pheromones);
+            while (!sel.HasValue) sel = SelectNextCity(curCity, ref visited, ref distances, ref pheromones);
+            if (sel is not { } nextCity) return null;
+            tour.Add(nextCity);
+            visited[nextCity] = true;
+            curCity = nextCity;
+        }
+
+        if (distances[curCity][tour[0]] == 0) return null;
+
+        tour.Add(tour[0]);
+
+        distance = CalculateTourDistance(tour, ref distances);
+
+        return (tour, distance);
+    }
+
+    /// <inheritdoc/>
+    public void UpdatePheromones(ref double[][] pheromones)
+    {
+        double deposit = Q / distance;
+        for (int i = 0; i < tour.Count - 1; i++)
+        {
+            int cityA = tour[i];
+            int cityB = tour[i + 1];
+            pheromones[cityA][cityB] += deposit;
+            pheromones[cityB][cityA] += deposit;  // Ненаправленный граф
+        }
+    }
+
+    private static double CalculateTourDistance(List<int> tour, ref double[][] distances)
+    {
+        double distance = 0;
+        for (int i = 0; i < tour.Count - 1; i++)
+        {
+            distance += distances[tour[i]][tour[i + 1]];
+        }
+        return distance;
+    }
+
+    private int? SelectNextCity(int currentCity, ref bool[] visited, ref double[][] distances, ref double[][] pheromones)
+    {
+        var probabilities = new double[distances.Length];
+        double probabilitiesSum = 0;
+
+        // Вероятности для каждого непосещенного города
+        for (int i = 0; i < distances.Length; 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 < distances.Length; i++)
+        {
+            if (!visited[i])
+            {
+                //sum += probabilities[i];
+                if (probabilities[i] >= randomValue)
+                {
+                    return i;
+                }
+            }
+        }
+
+        return null;
+    }
+}

+ 21 - 0
AntColony/Algorithm/IAnt.cs

@@ -0,0 +1,21 @@
+namespace AntColony.Algorithm;
+
+/// <summary>
+/// Муравей для муравьиного алгоритма
+/// </summary>
+public interface IAnt
+{
+    /// <summary>
+    /// Построить путь
+    /// </summary>
+    /// <param name="distances">Матрица ребер</param>
+    /// <param name="pheromones">Матрица феромонов</param>
+    /// <returns></returns>
+    (List<int> tour, double distance)? ConstructTour(ref double[][] distances, ref double[][] pheromones);
+
+    /// <summary>
+    /// Обновить феромоны
+    /// </summary>
+    /// <param name="pheromones">Матрица феромонов</param>
+    void UpdatePheromones(ref double[][] pheromones);
+}

+ 2 - 6
AntColony/Program.cs

@@ -29,12 +29,8 @@ class Program
 
         var aco = new AntColonyOptimizer(
             distances: distances,
-            numberOfAnts: ants,
-            alpha: alpha,
-            beta: beta,
-            evaporationRate: evaporationRate,
-            Q: q
-        );
+            evaporationRate: evaporationRate
+        ).AddAnts<DefaultAnt>(1500).ShuffleAnts();
 
         Console.CancelKeyPress += (e, f) =>
         {