In relation to another question I had, I have been researching Simulated Annealing.
The general example used with this algorithm is the traveling salesman example. I have been testing the code described here : https://jamesmccaffrey.wordpress.com/2021/12/01/the-traveling-salesman-problem-using-simulated-annealing-in-csharp/
I have noticed something rather annoying though.
If I set the number of cites to 60, with a maxIteration count of 200k, an alpha of 0.95 (rate at which temperature decreases) and a seed of 2, I get a good result.
It finds the optional solution in 143k iterations
If I then decrease the number of cities to 59 (with the same settings), I get a (very) poor result.
It maxes out the iterations with a solution more than twice the distance of the optimal solution
Simply changing the seed gives widely varying results.
I appreciate that this is to be expected due to the nature of randomness. But I am wondering if this is an implementation issue making things worse or just the nature of the algorithm?
Here is the C# code :
namespace TSP_Annealing
{
class TSP_Annealing_Program
{
static void Main(string[] args)
{
int nCities = 60;
Random rnd = new Random(2);
int maxIter = 200000;
double startTemperature = 10000.0;
double alpha = 0.95;
(int[] soln, int iteration) = Solve(nCities, rnd, maxIter, startTemperature, alpha);
Console.WriteLine($"Finished solve({iteration}) ");
Console.WriteLine("\nBest solution found: ");
ShowArray(soln);
double dist = TotalDist(soln);
Console.WriteLine("\nTotal distance = " + dist.ToString("F1"));
Console.ReadLine();
}
static double TotalDist(int[] route)
{
double d = 0.0; // total distance between cities
int n = route.Length;
for (int i = 0; i < n - 1; ++i)
{
if (route[i] < route[i + 1])
d += (route[i + 1] - route[i]) * 1.0;
else
d += (route[i] - route[i + 1]) * 1.5;
}
return d;
}
static double Error(int[] route)
{
int n = route.Length;
double d = TotalDist(route);
double minDist = n - 1;
return d - minDist;
}
static int[] Adjacent(int[] route, Random rnd)
{
int n = route.Length;
int[] result = (int[])route.Clone(); // shallow is OK
int i = rnd.Next(0, n); int j = rnd.Next(0, n);
int tmp = result[i];
result[i] = result[j]; result[j] = tmp;
return result;
}
static void Shuffle(int[] route, Random rnd)
{
// Fisher-Yates algorithm
int n = route.Length;
for (int i = 0; i < n; ++i)
{
int rIndx = rnd.Next(i, n);
int tmp = route[rIndx];
route[rIndx] = route[i];
route[i] = tmp;
}
}
static void ShowArray(int[] arr)
{
int n = arr.Length;
Console.Write("[ ");
for (int i = 0; i < n; ++i)
Console.Write(arr[i].ToString().PadLeft(2) + " ");
Console.WriteLine(" ]");
}
static (int[], int totalIterations) Solve(int nCities, Random rnd, int maxIter, double startTemperature, double alpha)
{
double currTemperature = startTemperature;
int[] soln = new int[nCities];
for (int i = 0; i < nCities; ++i) { soln[i] = i; }
Shuffle(soln, rnd);
Console.WriteLine("Initial guess: ");
ShowArray(soln);
double err = Error(soln);
int iteration = 0;
while (iteration < maxIter && err > 0.0)
{
int[] adjRoute = Adjacent(soln, rnd);
double adjErr = Error(adjRoute);
if (adjErr < err) // better route
{
soln = adjRoute; err = adjErr;
}
else
{
double acceptProb =
Math.Exp((err - adjErr) / currTemperature);
double p = rnd.NextDouble(); // corrected
if (p < acceptProb) // accept anyway
{
soln = adjRoute; err = adjErr;
}
}
if (currTemperature < 0.00001)
currTemperature = 0.00001;
else
currTemperature *= alpha;
++iteration;
}
return (soln, iteration);
}
}
}
Edit : I don't appear to be having much luck with this. As suggested, I tried writing the example again, but this time in 2D. To get the next route, I followed the suggestion in this tutorial https://youtu.be/AEeYp5VtI08?t=1174 which picks a random segment and reverses it.
The example seems to work when I set the random seed to 2 and finds the shortest distance of ~1695, but any other seed results in it getting stuck with distances much greater.
namespace TSP_Annealing
{
public class Map
{
public const int Size = 1000;
public List<Point> Cities = new();
public Map(int cities)
{
var r = new Random(0);
for (int i = 0; i < cities; i++)
{
int x = r.Next(0, Size);
int y = r.Next(0, Size);
Cities.Add(new Point(x, y));
}
}
}
public class TSM
{
private Map _map = new Map(8);
private const int _maxIterations = 20000;
private const double _startTemperature = 10000;
private const double _alpha = 0.95;
private Random _rnd = new Random(2);
public void Solve()
{
double currTemp = _startTemperature;
var shortestRoute = Shuffle(Enumerable.Range(0, _map.Cities.Count).ToArray());
double shortestDistance = ComputeDistance(shortestRoute);
int iteration = 0;
while (iteration < _maxIterations)
{
int[] nextRoute = NextRoute(shortestRoute);
double nextDistance = ComputeDistance(nextRoute);
if (nextDistance < shortestDistance)
{
shortestRoute = nextRoute;
shortestDistance = nextDistance;
}
else
{
double acceptProb = Math.Exp(-(nextDistance - shortestDistance) / currTemp);
double p = _rnd.NextDouble();
if (p < acceptProb)
{
shortestRoute = nextRoute;
shortestDistance = nextDistance;
}
}
if (currTemp < 0.00001)
currTemp = 0.00001;
else
currTemp *= _alpha;
++iteration;
}
Debug.WriteLine($"Shortest : {shortestDistance}, Iterations: {iteration}");
}
private double ComputeDistance(int[] route)
{
double total = 0.0;
for (int i = 0; i < route.Length - 1; ++i)
{
Point a = _map.Cities[route[i]];
Point b = _map.Cities[route[i + 1]];
double x = b.X - a.X;
double y = b.Y - a.Y;
double length = Math.Sqrt(x * x + y * y);
total += length;
}
return total;
}
private int[] NextRoute(int[] route)
{
// pick two random points to define a segment
int p1 = _rnd.Next(0, route.Length);
int p2 = _rnd.Next(p1, route.Length);
while ((p2 - p1) <= 1)
{
p1 = _rnd.Next(0, route.Length);
p2 = _rnd.Next(p1, route.Length);
}
// reverse the segment
var copy = (int[])route.Clone();
for (int i = 0; i < route.Length; ++i)
{
int n = p1 + i;
int m = p2 - i;
if (n >= m) break;
int temp = copy[n];
copy[n] = copy[m];
copy[m] = temp;
}
return copy;
}
private int[] Shuffle(int[] route)
{
int n = route.Length;
for (int i = 0; i < n; ++i)
{
int rIndx = _rnd.Next(i, n);
int tmp = route[rIndx];
route[rIndx] = route[i];
route[i] = tmp;
}
return route;
}
}
class TSP_Annealing_Program
{
static void Main(string[] args)
{
var tsm = new TSM();
tsm.Solve();
}
}
}