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 readonly double evaporationRate; private readonly double Q; // Множитель депозита феромонов private readonly int numberOfAnts; /// /// Инициализировать алгоритм /// /// Матрица расстояний /// Количество муравьев /// Значение α (определяет вес феромонов) /// Значение β (определяет вес расстояния) /// Скорость испарения /// Значение Q public AntColonyOptimizer(double[][] distances, int numberOfAnts = 20, double alpha = 1, double beta = 2, double evaporationRate = 0.1, double Q = 100) { 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][]; 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 (List bestTour, double bestDistance) Solve(int maxIterations) { List bestTourEver = []; double bestDistanceEver = double.MaxValue; Lock distanceLock = new(); Lock pheromoneLock = new(); // for (int iteration = 0; iteration < maxIterations; iteration++) Parallel.For(0, maxIterations, i => { List> antTours = []; List antDistances = []; // Каждый муравей строит путь for (int ant = 0; ant < numberOfAnts; ant++) { var ct = ConstructTour(); if (ct is null) continue; var (tour, distance) = ct.Value; antTours.Add(tour); antDistances.Add(distance); // Если текущий путь лучше - обновляем lock (distanceLock) { if (distance < bestDistanceEver) { bestDistanceEver = distance; bestTourEver = [.. tour]; } } } // Обновляем феромоны для всех путей lock (pheromoneLock) { UpdatePheromones(antTours, antDistances); } // File.AppendAllText("./iteration-results", bestDistanceEver.ToString() + '\n'); // } }); return (bestTourEver, bestDistanceEver); } private (List tour, double distance)? ConstructTour() { List 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); 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)); } /// /// Выбрать следующий город /// /// В каком городе муравей находится сейчас /// Какие города муравей уже посетил /// Номер следующего города 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; } /// /// Обновить матрицу феромонов /// /// Пути муравьев /// Их расстояния private void UpdatePheromones(List> antTours, List antDistances) { // Испарение for (int i = 0; i < numberOfCities; i++) { for (int j = 0; j < numberOfCities; j++) { if (i != j) 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; // Граф ненаправленный } } } /// /// Рассчитать длину пути /// /// Путь /// Длина пути private double CalculateTourDistance(List tour) { double distance = 0; for (int i = 0; i < tour.Count - 1; i++) { distance += distances[tour[i]][tour[i + 1]]; } return distance; } }