OsmAnd/OsmAnd-java/src/net/osmand/TspAnt.java

327 lines
9.7 KiB
Java
Raw Normal View History

package net.osmand;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import net.osmand.data.LatLon;
import net.osmand.util.MapUtils;
/*
* === Implementation of ant swarm TSP solver. ===
*
* The algorithm is described in [1, page 8].
*
* == Tweaks/notes ==
* - I added a system where the ant chooses with probability
* "pr" to go to a purely random town. This did not yield better
* results so I left "pr" fairly low.
* - Used an approximate pow function - the speedup is
* more than a factor of 10! And accuracy is not needed
* See AntTsp.pow for details.
*
* == Parameters ==
* I set the parameters to values suggested in [1]. My own experimentation
* showed that they are pretty good.
*
* == Usage ==
* - Compile: javac AntTsp.java
* - Run: java AntTsp <TSP file>
*
* == TSP file format ==
* Full adjacency matrix. Columns separated by spaces, rows by newline.
* Weights parsed as doubles, must be >= 0.
*
* == References ==
* [1] M. Dorigo, The Ant System: Optimization by a colony of cooperating agents
* ftp://iridia.ulb.ac.be/pub/mdorigo/journals/IJ.10-SMC96.pdf
*
*/
// https://github.com/lukedodd/ant-tsp
public class TspAnt {
// Algorithm parameters:
// original amount of trail
private double c = 1.0;
// trail preference
private double alpha = 1;
// greedy preference
private double beta = 5;
// trail evaporation coefficient
private double evaporation = 0.5;
// new trail deposit coefficient;
private double Q = 500;
// number of ants used = numAntFactor*numTowns
private double numAntFactor = 0.8;
// probability of pure random selection of the next town
private double pr = 0.01;
// Reasonable number of iterations
// - results typically settle down by 500
private int maxIterations = 2000;
public int n = 0; // # towns
public int m = 0; // # ants
private double graph[][] = null;
private double trails[][] = null;
private Ant ants[] = null;
private Random rand = new Random();
private double probs[] = null;
private int currentIndex = 0;
public int[] bestTour;
public double bestTourLength;
// Ant class. Maintains tour and tabu information.
private class Ant {
public int tour[] = new int[graph.length];
// Maintain visited list for towns, much faster
// than checking if in tour so far.
public boolean visited[] = new boolean[graph.length];
public void visitTown(int town) {
tour[currentIndex + 1] = town;
visited[town] = true;
}
public boolean visited(int i) {
return visited[i];
}
public double tourLength() {
double length = graph[tour[n - 1]][tour[0]];
for (int i = 0; i < n - 1; i++) {
length += graph[tour[i]][tour[i + 1]];
}
return length;
}
public void clear() {
for (int i = 0; i < n; i++)
visited[i] = false;
}
}
// Read in graph from a file.
// Allocates all memory.
// Adds 1 to edge lengths to ensure no zero length edges.
public TspAnt readGraph(List<LatLon> intermediates, LatLon start, LatLon end) {
List<LatLon> l = new ArrayList<LatLon>();
l.add(start);
l.addAll(intermediates);
l.add(end);
n = l.size() ;
// System.out.println("Cost");
graph = new double[n][n];
double maxSum = 0;
for (int i = 0; i < n ; i++) {
double maxIWeight = 0;
for (int j = 1; j < n ; j++) {
double d = Math.rint(MapUtils.getDistance(l.get(i), l.get(j))) + 0.1;
maxIWeight = Math.max(d, maxIWeight);
graph[i][j] = d;
}
maxSum += maxIWeight;
}
maxSum = Math.rint(maxSum) + 1;
for (int i = 0; i < n; i++) {
if (i == n - 1) {
graph[i][0] = 0.1;
} else {
graph[i][0] = maxSum;
}
// System.out.println(Arrays.toString(graph[i]));
}
m = (int) (n * numAntFactor);
// all memory allocations done here
trails = new double[n][n];
probs = new double[n];
ants = new Ant[m];
for (int j = 0; j < m; j++)
ants[j] = new Ant();
return this;
}
// Approximate power function, Math.pow is quite slow and we don't need accuracy.
// See:
// http://martin.ankerl.com/2007/10/04/optimized-pow-approximation-for-java-and-c-c/
// Important facts:
// - >25 times faster
// - Extreme cases can lead to error of 25% - but usually less.
// - Does not harm results -- not surprising for a stochastic algorithm.
public static double pow(final double a, final double b) {
final int x = (int) (Double.doubleToLongBits(a) >> 32);
final int y = (int) (b * (x - 1072632447) + 1072632447);
return Double.longBitsToDouble(((long) y) << 32);
}
// Store in probs array the probability of moving to each town
// [1] describes how these are calculated.
// In short: ants like to follow stronger and shorter trails more.
private void probTo(Ant ant) {
int i = ant.tour[currentIndex];
double denom = 0.0;
for (int l = 0; l < n; l++)
if (!ant.visited(l))
denom += pow(trails[i][l], alpha)
* pow(1.0 / graph[i][l], beta);
for (int j = 0; j < n; j++) {
if (ant.visited(j)) {
probs[j] = 0.0;
} else {
double numerator = pow(trails[i][j], alpha)
* pow(1.0 / graph[i][j], beta);
probs[j] = numerator / denom;
}
}
}
// Given an ant select the next town based on the probabilities
// we assign to each town. With pr probability chooses
// totally randomly (taking into account tabu list).
private int selectNextTown(Ant ant) {
// sometimes just randomly select
if (rand.nextDouble() < pr) {
int t = rand.nextInt(n - currentIndex); // random town
int j = -1;
for (int i = 0; i < n; i++) {
if (!ant.visited(i))
j++;
if (j == t)
return i;
}
}
// calculate probabilities for each town (stored in probs)
probTo(ant);
// randomly select according to probs
double r = rand.nextDouble();
double tot = 0;
for (int i = 0; i < n; i++) {
tot += probs[i];
if (tot >= r)
return i;
}
throw new RuntimeException("Not supposed to get here.");
}
// Update trails based on ants tours
private void updateTrails() {
// evaporation
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
trails[i][j] *= evaporation;
// each ants contribution
for (Ant a : ants) {
double contribution = Q / a.tourLength();
for (int i = 0; i < n - 1; i++) {
trails[a.tour[i]][a.tour[i + 1]] += contribution;
}
trails[a.tour[n - 1]][a.tour[0]] += contribution;
}
}
// Choose the next town for all ants
private void moveAnts() {
// each ant follows trails...
while (currentIndex < n - 1) {
for (Ant a : ants)
a.visitTown(selectNextTown(a));
currentIndex++;
}
}
// m ants with random start city
private void setupAnts() {
currentIndex = -1;
for (int i = 0; i < m; i++) {
ants[i].clear(); // faster than fresh allocations.
ants[i].visitTown(rand.nextInt(n));
}
currentIndex++;
}
private void updateBest() {
if (bestTour == null) {
bestTour = ants[0].tour;
bestTourLength = ants[0].tourLength();
}
for (Ant a : ants) {
if (a.tourLength() < bestTourLength) {
bestTourLength = a.tourLength();
bestTour = a.tour.clone();
}
}
}
public static String tourToString(int tour[]) {
2013-07-31 20:07:07 +02:00
String t = "";
for (int i : tour)
t = t + " " + i;
return t;
}
public int[] solve() {
// clear trails
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
trails[i][j] = c;
int iteration = 0;
// run for maxIterations
// preserve best tour
while (iteration < maxIterations) {
setupAnts();
moveAnts();
updateTrails();
updateBest();
iteration++;
}
// Subtract n because we added one to edges on load
System.out.println("Best tour length: " + (bestTourLength - n*0.1));
System.out.println("Best tour:" + tourToString(bestTour));
return alignAnswer(bestTour.clone());
}
private static int[] alignAnswer(int[] ans) {
int[] alignAns = new int[ans.length];
int shift = 0;
for(int j = 0; j < ans.length; j++) {
if(ans[j] == 0) {
shift = j;
break;
}
}
for (int j = 0; j < ans.length; j++) {
alignAns[(j - shift + ans.length) % ans.length] = ans[j];
}
return alignAns;
}
// Load graph file given on args[0].
// (Full adjacency matrix. Columns separated by spaces, rows by newlines.)
// Solve the TSP repeatedly for maxIterations
// printing best tour so far each time.
public static void main(String[] args) {
// Load in TSP data file.
if (args.length < 1) {
System.err.println("Please specify a TSP data file.");
return;
}
TspAnt anttsp = new TspAnt();
// Repeatedly solve - will keep the best tour found.
for (; ; ) {
anttsp.solve();
}
}
}