/// <summary> /// Combine both A -> Z and Round Trip with optimize by many algorithm and distance, /// duration by Google Matrix /// </summary> /// <param name="coordinates"> </param> /// <param name="mode"> </param> /// <param name="googleApiKey"> /// Use for FastestTripMode.RoundTrip - Optional, method still work without key but have /// limitation by Google Policy. /// </param> /// <remarks> /// Concorde TSP Solver algorithm combine with Ant colony optimization algorithms to find /// wayCoordinate and best path /// </remarks> public FastestTrip(IReadOnlyCollection <Models.Coordinate> coordinates, FastestTripMode mode = FastestTripMode.AtoZ, string googleApiKey = "") { if (coordinates.Count < 1) { throw new NotSupportedException(); } _tripMode = mode; _googleApiKey = googleApiKey; Coordinates = new List <Models.Coordinate>(); Coordinates.AddRange(coordinates); // Get Distance Duration Matrix var coordinateModels = Coordinates.Select(x => new CoordinateModel(x.Longitude, x.Latitude)).ToArray(); var getDistanceDurationMatrixTask = GoogleMapHelper.GetDistanceDurationMatrixAsync(coordinateModels, coordinateModels, googleApiKey: _googleApiKey); getDistanceDurationMatrixTask.Wait(); DistanceDurationDurationMatrix = getDistanceDurationMatrixTask.Result; // Start Calculate Trip CalculateTrip(mode); }
private void CalculateTripBackTrackingImplementation(int start, FastestTripMode mode) { _currentCost = 0; _bestCost = MaxTripSentry; _currentPath = new int[Coordinates.Count]; _visitTracks = new bool[Coordinates.Count]; _min = new double[Coordinates.Count]; for (int i = 0; i < Coordinates.Count; i++) { _min[i] = GetMinDistance(i); } _minCost = _min.Sum(); if (mode == FastestTripMode.AtoZ) { _minCost -= _min[start]; } _currentPath[0] = start; _visitTracks[start] = true; BackTrackingVisit(1); }
private void CalculateTrip(FastestTripMode mode) { if (Coordinates.Count <= 13) { CalculateTripBackTrackingImplementation(0, mode); } else if (Coordinates.Count <= 15) { TspDynamic(mode); } else { TspAntColonyK2(mode); TspK3(); } }
/// <summary> /// Ant colony optimization algorithms and Solves the TSP problem to optimality. Memory /// requirement is O(numActive * 2^numActive) /// </summary> private void TspDynamic(FastestTripMode mode) { BestPath = new List <int>(); NextSet = new List <int>(); BestTrip = MaxTripSentry; int numActive = Coordinates.Count; var numCombos = 1 << Coordinates.Count; var c = new List <List <double> >(); var parent = new List <List <int> >(); for (var i = 0; i < numCombos; i++) { c.Add(new List <double>()); parent.Add(new List <int>()); for (var j = 0; j < numActive; ++j) { c[i].Add(0.0); parent[i].Add(0); } } int index; for (var k = 1; k < numActive; ++k) { index = 1 + (1 << k); c[index][k] = GetDistance(0, k); } for (var s = 3; s <= numActive; ++s) { for (var i = 0; i < numActive; ++i) { NextSet.Add(0); } index = NextSetOf(s); while (index >= 0) { for (var k = 1; k < numActive; ++k) { if (NextSet[k] != 0) { var previousIndex = index - (1 << k); c[index][k] = MaxTripSentry; for (var m = 1; m < numActive; ++m) { if (NextSet[m] != 0 && m != k) { if (c[previousIndex][m] + GetDistance(m, k) < c[index][k]) { c[index][k] = c[previousIndex][m] + GetDistance(m, k); parent[index][k] = m; } } } } } index = NextSetOf(s); } } for (var i = 0; i < numActive; ++i) { BestPath.Add(0); } index = (1 << numActive) - 1; int currentNode; // Case RoundTrip (A -> A), A -> Z START if (mode == FastestTripMode.RoundTrip) { currentNode = -1; BestPath.Add(0); for (var i = 1; i < numActive; ++i) { if (c[index][i] + GetDistance(i, 0) < BestTrip) { BestTrip = c[index][i] + GetDistance(i, 0); currentNode = i; } } BestPath[numActive - 1] = currentNode; } else { currentNode = numActive - 1; BestPath[numActive - 1] = numActive - 1; BestTrip = c[index][numActive - 1]; } // Case A->A, A->Z END for (var i = numActive - 1; i > 0; --i) { currentNode = parent[index][currentNode]; index -= (1 << BestPath[i]); BestPath[i - 1] = currentNode; } }
/// <summary> /// Computes a near-optimal solution to the TSP problem, using Ant Colony Optimization /// and local optimization in the form of k2-opting each candidate route. Run time is /// O(numWaves * numAnts * numActive ^ 2) for ACO and O(numWaves * numAnts * numActive ^ /// 3) for rewiring? if mode is 1, we start at node 0 and end at node numActive-1. /// </summary> /// <param name="mode"></param> private void TspAntColonyK2(FastestTripMode mode) { BestTrip = MaxTripSentry; int numActive = Coordinates.Count; _currentPath = new int[numActive]; _visitTracks = new bool[numActive]; var currentPath = new int[numActive]; if (mode == FastestTripMode.RoundTrip) { currentPath = new int[numActive + 1]; } var alpha = 0.1; // The importance of the previous trails var beta = 2.0; // The importance of the durations var rho = 0.1; // The decay rate of the pheromone trails var asymptoteFactor = 0.9; // The sharpness of the reward as the solutions approach the best solution double[,] pher = new double[numActive, numActive]; double[,] nextPher = new double[numActive, numActive]; double[] prob = new double[numActive]; var numAnts = 20; var numWaves = 20; for (var i = 0; i < numActive; ++i) { for (var j = 0; j < numActive; ++j) { pher[i, j] = 1; nextPher[i, j] = 0.0; } } var lastNode = 0; const int startNode = 0; var numSteps = numActive - 1; var numValidDest = numActive; if (mode == FastestTripMode.AtoZ) { lastNode = numActive - 1; numSteps = numActive - 2; numValidDest = numActive - 1; } for (var wave = 0; wave < numWaves; ++wave) { for (var ant = 0; ant < numAnts; ++ant) { var currentNode = startNode; var currentDistance = 0; for (var i = 0; i < numActive; ++i) { _visitTracks[i] = false; } currentPath[0] = currentNode; for (var step = 0; step < numSteps; ++step) { _visitTracks[currentNode] = true; var cumProb = 0.0; for (var next = 1; next < numValidDest; ++next) { if (!_visitTracks[next]) { prob[next] = Math.Pow(pher[currentNode, next], alpha) * Math.Pow(GetDuration(currentNode, next), 0.0 - beta); cumProb += prob[next]; } } var guess = new Random().NextDouble() * cumProb; var nextI = -1; for (var next = 1; next < numValidDest; ++next) { if (!_visitTracks[next]) { nextI = next; guess -= prob[next]; if (guess < 0) { nextI = next; break; } } } currentDistance += (int)GetDuration(currentNode, nextI); currentPath[step + 1] = nextI; currentNode = nextI; } currentPath[numSteps + 1] = lastNode; currentDistance += (int)GetDuration(currentNode, lastNode); // k2-rewire: var lastStep = numActive; if (mode == FastestTripMode.AtoZ) { lastStep = numActive - 1; } var changed = true; var m = 0; while (changed) { changed = false; for (; m < lastStep - 2 && !changed; ++m) { var cost = GetDuration(currentPath[m + 1], currentPath[m + 2]); var revCost = GetDuration(currentPath[m + 2], currentPath[m + 1]); var iCost = GetDuration(currentPath[m], currentPath[m + 1]); for (var j = m + 2; j < lastStep && !changed; ++j) { var nowCost = cost + iCost + GetDuration(currentPath[j], currentPath[j + 1]); var newCost = revCost + GetDuration(currentPath[m], currentPath[j]) + GetDuration(currentPath[m + 1], currentPath[j + 1]); if (nowCost > newCost) { currentDistance += (int)(newCost - nowCost); // Reverse the detached road segment. for (var k = 0; k < Math.Floor((double)(j - m) / 2); ++k) { double tmp = currentPath[m + 1 + k]; currentPath[m + 1 + k] = currentPath[j - k]; currentPath[j - k] = (int)tmp; } changed = true; --m; } cost += GetDuration(currentPath[j], currentPath[j + 1]); revCost += GetDuration(currentPath[j + 1], currentPath[j]); } } } if (currentDistance < BestTrip) { BestPath = currentPath.ToList(); BestTrip = currentDistance; } for (var i = 0; i <= numSteps; ++i) { nextPher[currentPath[i], currentPath[i + 1]] += (BestTrip - asymptoteFactor * BestTrip) / (numAnts * (currentDistance - asymptoteFactor * BestTrip)); } } for (var i = 0; i < numActive; ++i) { for (var j = 0; j < numActive; ++j) { pher[i, j] = pher[i, j] * (1.0 - rho) + rho * nextPher[i, j]; nextPher[i, j] = 0.0; } } } }