Traveling salesman is a classic optimization problem which is Permutation/Combination problem to find an optimal shortest path for a salesman to visit all the cities exactly once.
Each City has an X and Y coordinates
The Genes in Genetic Algorithm would be the city index, and the Chromosomes would be an array or genes, hence it would contain the order of city to visit.
The program would have an array of cities (in FitnessCalc Class). The Population class holds an array of Chromosomes(which is an array of order of city to visit).
The GA_TSPVer2 class has methods to mutate and crossover methods to randomly shuffle the genes in order to improve the next generation of population.
City Class
public class City
{
public int X { get; set; }
public int Y { get; set; }
public City(int x, int y)
{
X = x; Y = y;
}
public int GetDistance(City otherCity)
{
int deltaX = X - otherCity.X;
int deltaY = Y - otherCity.Y;
return (int)Math.Sqrt(deltaX * deltaX + deltaY * deltaY);
}
}
Chromosomes: Contains Genes which are the order of the cities to visit
public class Chromosomes
{
public static int DefaultGeneLength = 10;
private int[] Genes = new int[DefaultGeneLength];
public int[] GetGenes()
{
return Genes;
}
private int Fitness = 0;
public void GenerateRandom()
{
int timesZero = 0;
for (int i = 0; i < Size(); i++)
{
bool repeat = false;
do
{
int index = (int)(new Random(Guid.NewGuid().GetHashCode()).NextDouble() * (double)Size());
if(Genes.Contains(index))
{
repeat = true;
if(index == 0){
if(timesZero ==0)
{
timesZero = 1;
Genes[i] = index;
repeat = false;
}
}
}
else{
repeat = false;
Genes[i] = index;
}
}while(repeat == true);
}
}
public int GetGene(int index)
{
return Genes[index];
}
public void SetGene(int index, int value)
{
Genes[index] = value;
Fitness = 0;
}
public int GetFitness()
{
if (Fitness == 0)
{
Fitness = FitnessCalc.GetFitness(this);
}
return Fitness;
}
public int Size()
{
return Genes.Length;
}
public string Show(City[] cities)
{
StringBuilder sbuilder = new StringBuilder();
foreach (int i in Genes)
{
sbuilder.Append(cities[i].X + "," + cities[i].Y + ">>");
}
sbuilder.Append("Total Cost:" + GetFitness());
return sbuilder.ToString();
}
}
Fitness Calculator: Which is the total distance traveled in the the order of genes
public class FitnessCalc
{
static public City[] cities;
public static void Initialize()
{
cities = new City[Chromosomes.DefaultGeneLength];
cities[0] = new City(150, 190);
cities[1] = new City(150, 73);
cities[2] = new City(150, 388);
cities[3] = new City(150, 65);
cities[4] = new City(150, 330);
cities[5] = new City(150, 5);
cities[6] = new City(150, 57);
cities[7] = new City(150, 380);
cities[8] = new City(150, 147);
cities[9] = new City(150, 251);
}
internal static int GetFitness(Chromosomes chromosomes)
{
int count = chromosomes.GetGenes().Where(i => i == 0).Count();
if (count > 1)
return 99999;
int cost = 0;
for (int i = 0; i < chromosomes.Size() - 1; i++)
{
int dist = cities[chromosomes.GetGene(i)].GetDistance(cities[chromosomes.GetGene(i + 1)]);
cost += dist;
}
return cost;
}
}
Population: Holds the Population of chromosomes
public class Population
{
Chromosomes[] individuals;
public Population(int populationSize, bool initialize)
{
individuals = new Chromosomes[populationSize];
if (initialize)
{
for (int i = 0; i < Size(); i++)
{
Chromosomes newIndividual = new Chromosomes();
newIndividual.GenerateRandom();
SaveIndividual(i, newIndividual);
}
}
}
public Chromosomes GetIndividual(int index)
{
return individuals[index];
}
public Chromosomes GetFittest()
{
return individuals.Aggregate((i1, i2) => i1.GetFitness() < i2.GetFitness() ? i1 : i2);
}
public int Size()
{
return individuals.Length;
}
public void SaveIndividual(int index, Chromosomes individual)
{
individuals[index] = individual;
individual.GetFitness();
}
}
Genetic Algorithm Logic
My genetic algorithm involves:
1. crossover by getting a gene from Chromosome1 else Chromosome2
2. mutation by shuffling the order of a gene in a Chromosome.
I have two types crossover method,
first method - it loops over each gene and copy a gene either from Chromosome1 else Chromosome2.
second method(commented) - randomly selects an index and copies all genes left of that index from Chromosome1 and copies all genes right of that index from Chromosome2.
public class GA_TSPVer2
{
private static double uniformRate = 0.5;
private static double mutationRate = 0.05;
private static int tournamentSize = 5;
private static bool elitism = true;
public static Population Evolve(Population pop)
{
Population newPopulation = new Population(pop.Size(), false);
if (elitism)
{
newPopulation.SaveIndividual(0, pop.GetFittest());
}
int elitismOffset;
if (elitism)
{
elitismOffset = 1;
}
else
{
elitismOffset = 0;
}
for (int i = elitismOffset; i < pop.Size(); i++)
{
Chromosomes indiv1 = TournamentSelection(pop);
Chromosomes indiv2 = TournamentSelection(pop);
Chromosomes newIndiv = Crossover(indiv1, indiv2);
newPopulation.SaveIndividual(i, newIndiv);
}
for (int i = elitismOffset; i < newPopulation.Size(); i++)
{
Mutate(newPopulation.GetIndividual(i));
}
return newPopulation;
}
private static Chromosomes Crossover(Chromosomes indiv1, Chromosomes indiv2)
{
Chromosomes newSol = new Chromosomes();
for (int i = 0; i < newSol.GetGenes().Length; i++)
{
newSol.SetGene(i, -1);
}
for (int j = 0; j < newSol.Size(); j++)
{
double toss = (new Random(Guid.NewGuid().GetHashCode()).NextDouble());
if (toss > uniformRate)
{
int Gene = 0;
int geneLeft = indiv2.GetGenes().Length - j;
int counter = j;
do{
if(geneLeft == 0)
{
Gene = indiv2.GetGenes().Except(newSol.GetGenes()).First();
}
else {
Gene = indiv2.GetGenes().ElementAt(counter++);
geneLeft--;
}
bool b = newSol.GetGenes().Contains(Gene);
}while(newSol.GetGenes().Contains(Gene));
newSol.SetGene(j, Gene);
}
else
{
int Gene = 0;
int geneLeft = indiv1.GetGenes().Length - j;
int counter = j;
do
{
if (geneLeft == 0)
{
Gene = indiv1.GetGenes().Except(newSol.GetGenes()).First();
}
else
{
Gene = indiv1.GetGenes().ElementAt(counter++);
geneLeft--;
}
bool b = newSol.GetGenes().Contains(Gene);
} while (newSol.GetGenes().Contains(Gene));
newSol.SetGene(j, Gene);
}
}
return newSol;
}
private static void Mutate(Chromosomes indiv)
{
for (int i = 0; i < indiv.Size(); i++)
{
double d = new Random(Guid.NewGuid().GetHashCode()).NextDouble();
if (d <= mutationRate)
{
int sindex = (int)(new Random(Guid.NewGuid().GetHashCode()).NextDouble() * indiv.Size());
int sValue = indiv.GetGene(sindex);
int eindex;
do
{
eindex = (int)(new Random(Guid.NewGuid().GetHashCode()).NextDouble() * indiv.Size());
} while (sindex == eindex);
int eValue = indiv.GetGene(eindex);
indiv.SetGene(sindex, eValue);
indiv.SetGene(eindex, sValue);
}
}
}
private static Chromosomes TournamentSelection(Population pop)
{
Population tournament = new Population(tournamentSize, false);
for (int i = 0; i < tournamentSize; i++)
{
double d = new Random(Guid.NewGuid().GetHashCode()).NextDouble();
int randomId = (int)(d * pop.Size());
tournament.SaveIndividual(i, pop.GetIndividual(randomId));
}
Chromosomes fittest = tournament.GetFittest();
return fittest;
}
}
Main Class
public class Consoles
{
public static void Main()
{
TSPVer2.FitnessCalc.Initialize();
TSPVer2.Population myPop = new TSPVer2.Population(10, true);
int generationCount = 0;
int sameCount = 0;
int thisCost = 0;
int oldCost = 0;
while (sameCount < 200)
{
generationCount++;
myPop = TSPVer2.GA_TSPVer2.Evolve(myPop);
thisCost = myPop.GetFittest().GetFitness();
if ((int)thisCost == (int)oldCost)
{
sameCount++;
}
else
{
sameCount = 0;
oldCost = thisCost;
}
}
Console.WriteLine("Gen:"+generationCount);
Console.WriteLine(myPop.GetFittest().Show(TSPVer2.FitnessCalc.cities));
Console.Read();
}
}
**Output:**
150,251>>150,388>>150,380>>150,330>>150,190>>150,147>>150,73>>150,65>>150,57>>150,5>>Total Cost:520
Since its a straight line, meaning the shortest path is always an ordered points.
Problem:
The problem I am facing is that the programs does try to what it is suppose to do, i.e., try to find the shortest path and saves the shortest path it found for the next generation population, however, I suppose the crossover and/or the mutation technique that I have applied is inefficient/incapable of doing a better job of finding the shortest path, perhaps its the algorithm or maybe I need to add another logic.
I do realize that Genetic algorithm gives an optimal solution not necessarily the best solution, but in my case its not giving an optimal solution either, it gets stuck on some solution and is unable to find a better solution, after evolving over a few generations.
What I have tried:
I have tried adjusting the mutation and crossover rate and population but didn't work
I have also used two different crossover technique