Some sample C# code on how a genetic algorithm can be applied to the linear assignment problem.
This problem can be efficiently solved using the Hungarian algorithm, but I wanted to demonstrate how the genetic algorithm can produce an optimal solution too.
This example is implemented as a simple console application using code developed in Microsoft Visual Studio. To help break down the problem a little, I develop a number of classes for the purposes of encoding potential solutions as chromosomes, maintaining a population of chromosome and applying genetic operators to the population of solutions in the form of crossover, mutation and selection.
Code samples as follows:
Program.cs
The main program loop:
using System;
namespace LinearAssignmentProblem
{
class Program
{
static void Main(string[] args)
{
var tasks = 5;
var popSize = 100;
var rnd = new Random();
// Do we seek to maximise or minimise?
var maximise = false;
var alg = new Algorithm(rnd, popSize, tasks, maximise);
var matrix = new CostMatrix(tasks);
matrix.SetCost(0, 0, 11);
matrix.SetCost(0, 1, 7);
matrix.SetCost(0, 2, 10);
matrix.SetCost(0, 3, 17);
matrix.SetCost(0, 4, 10);
matrix.SetCost(1, 0, 13);
matrix.SetCost(1, 1, 21);
matrix.SetCost(1, 2, 7);
matrix.SetCost(1, 3, 11);
matrix.SetCost(1, 4, 13);
matrix.SetCost(2, 0, 13);
matrix.SetCost(2, 1, 13);
matrix.SetCost(2, 2, 15);
matrix.SetCost(2, 3, 13);
matrix.SetCost(2, 4, 14);
matrix.SetCost(3, 0, 18);
matrix.SetCost(3, 1, 10);
matrix.SetCost(3, 2, 13);
matrix.SetCost(3, 3, 16);
matrix.SetCost(3, 4, 14);
matrix.SetCost(4, 0, 12);
matrix.SetCost(4, 1, 8);
matrix.SetCost(4, 2, 16);
matrix.SetCost(4, 3, 19);
matrix.SetCost(4, 4, 10);
alg.Run(rnd, matrix, tasks);
}
}
}
CostMatrix.cs
Used to maintain the cost of each task for each worker.
using System;
using System.Collections.Generic;
namespace LinearAssignmentProblem
{
public class CostMatrix
{
private int[] _agents;
private int[,] _costArray;
private int _n;
public CostMatrix(int agents)
{
_agents = new int[agents];
_costArray = new int[agents, agents];
_n = agents;
for (int i = 0; i < agents; ++i)
{
for (int j = 0; j < agents; ++j)
{
SetCost(i, j, 0);
}
}
}
public CostMatrix(int agents, int cost)
{
_agents = new int[agents];
_costArray = new int[agents, agents];
_n = agents;
for (int i = 0; i < agents; ++i)
{
for (int j = 0; j < agents; ++j)
{
int value = cost * (j + 1);
SetCost(i, j, value);
}
cost += 1;
}
}
// Generate cost matrix of arbitrary costs
public CostMatrix(int agents, int tasks, Random rnd)
{
_agents = new int[agents];
_costArray = new int[agents, agents];
_n = agents;
for (int i = 0; i < agents; ++i)
{
for (int j = 0; j < tasks; ++j)
{
SetCost(i, j, rnd.Next(10, 100));
}
}
}
public void SetCost(int agent, int task, int cost)
{
if (agent < _n && task < _n)
{
_costArray[agent, task] = cost;
}
}
public int GetCost(int worker, int task)
{
return _costArray[worker, task];
}
public int GetChromosomeCost(Chromosome chromosome, bool maximise)
{
var totalCost = 0;
var assignments = new HashSet<int>();
for (int worker = 0; worker < _n; ++worker)
{
var task = chromosome.GetTask(worker);
assignments.Add(task);
totalCost += GetCost(worker, task);
}
// Penalise cost asccording to constraint violations
var violations = _n - assignments.Count;
return maximise ? totalCost - violations * 100 :
totalCost + violations * 100;
}
}
}
Chromosome.cs
Used to encode solutions.
using System;
namespace LinearAssignmentProblem
{
public class Chromosome
{
private int[] _workers;
private int _cost;
private int _size;
public Chromosome(Random rnd, int workers)
{
_size = workers;
_workers = new int[workers];
Generate(rnd, workers);
}
public Chromosome(int workers)
{
_size = workers;
_workers = new int[workers];
}
public void Print(int iteration)
{
Console.WriteLine("Iteration = " + iteration);
Console.WriteLine("Total cost = " + _cost);
for(var i = 0; i < _size; ++i)
{
Console.WriteLine("Worker[" + i + "] -> Task[" + _workers[i] + "]");
}
Console.WriteLine();
}
public Chromosome Crossover(Chromosome chr, Random rnd)
{
var child = new Chromosome(_size);
int index1 = rnd.Next(0, _size);
int index2 = rnd.Next(0, _size);
var diff = Math.Abs(index1 - index2);
while (index1 == index2 || diff + 1 >= _size )
{
index2 = rnd.Next(0, _size);
diff = Math.Abs(index1 - index2);
}
if (index2 < index1)
{
int tmp = index1;
index1 = index2;
index2 = tmp;
}
for (int i = 0; i < index1; ++i)
{
var task = GetTask(i);
child.Assign(i, task);
}
for (int i = index1; i <= index2; ++i)
{
var task = chr.GetTask(i);
child.Assign(i, task);
}
for (int i = index2 + 1; i < _size; ++i)
{
var task = GetTask(i);
child.Assign(i, task);
}
return child;
}
public void Mutation(Random rnd)
{
for (int i = 0; i < _size; ++i)
{
if (rnd.Next(0, 100) < 33)
Assign(i, GetRandomTask(rnd, _size));
}
}
public void Copy(Chromosome chr)
{
_cost = chr._cost;
_size = chr._size;
for(var i = 0; i < _size; ++i)
{
_workers[i] = chr._workers[i];
}
}
public int WorkerCost(int worker)
{
return _workers[worker];
}
public int Cost()
{
return _cost;
}
public int Size()
{
return _size;
}
public void SetCost(int cost)
{
_cost = cost;
}
public void Assign(int worker, int task)
{
_workers[worker] = task;
}
public int GetTask(int worker)
{
return _workers[worker];
}
public int GetRandomTask(Random rnd, int taskRange)
{
return rnd.Next(0, taskRange);
}
public void Generate(Random rnd, int taskRange)
{
int count = 0;
foreach (var worker in _workers)
{
Assign(count, rnd.Next(0, taskRange));
count++;
}
}
}
}
Population.cs
Used to maintain a population of solutions.
using System;
using System.Collections.Generic;
namespace LinearAssignmentProblem
{
public class Population
{
private List<Chromosome> _chromosomes;
private long _bestChromosomeCost;
private int _bestChromosomeIndex;
private Chromosome _bestChromosome;
private bool _maximise;
public Population(Random rnd, int populationSize, int taskSize, bool maximise=true)
{
_bestChromosomeCost = maximise ? -1 : 9999999999;
_bestChromosomeIndex = -1;
_chromosomes = new List<Chromosome>(populationSize);
_maximise = maximise;
CreateArbitraryPopulation(rnd, populationSize, taskSize);
}
public void CreateArbitraryPopulation(Random rnd, int populationSize, int taskSize)
{
for(var i = 0; i < populationSize; ++i)
{
_chromosomes.Add(new Chromosome(rnd, taskSize));
}
}
public void Evaluate(CostMatrix costMatrix, int iteration)
{
for (var i = 0; i < _chromosomes.Count; ++i)
{
var cost = costMatrix.GetChromosomeCost(_chromosomes[i], _maximise);
_chromosomes[i].SetCost(cost);
if (IsBetter(cost, _bestChromosomeCost))
{
_bestChromosomeCost = cost;
_bestChromosomeIndex = i;
_chromosomes[_bestChromosomeIndex].Print(iteration);
}
}
}
public void ApplyCrossover(Random rnd, int taskSize)
{
var size = _chromosomes.Count;
foreach (var chromosome in _chromosomes)
{
var prob = rnd.Next(0, 100);
if (prob < 50)
{
var index1 = rnd.Next(0, size);
var index2 = rnd.Next(0, size);
while (index1 == index2)
{
index2 = rnd.Next(0, size);
}
Crossover(index1, index2, rnd, taskSize);
}
}
}
public void Crossover(int parentIndex1, int parentIndex2, Random rnd, int taskSize)
{
var chr1 = _chromosomes[parentIndex1];
var chr2 = _chromosomes[parentIndex2];
var child1 = chr1.Crossover(chr2, rnd);
var child2 = chr2.Crossover(chr1, rnd);
_chromosomes[parentIndex1].Copy(child1);
_chromosomes[parentIndex2].Copy(child2);
}
public void Mutate(Random rnd)
{
foreach(var chromosome in _chromosomes)
{
var prob = rnd.Next(0, 100);
if (prob < 5)
{
chromosome.Mutation(rnd);
}
}
}
public void StoreBestSolution(int taskSize)
{
_bestChromosome = new Chromosome(taskSize);
_bestChromosome.Copy(_chromosomes[_bestChromosomeIndex]);
}
public void SeedBestSolution(Random rnd)
{
var index = rnd.Next(0, _chromosomes.Count);
while (index == _bestChromosomeIndex)
{
index = rnd.Next(0, _chromosomes.Count);
}
_chromosomes[index].Copy(_bestChromosome);
}
public void Selection(Random rnd)
{
var size = _chromosomes.Count;
for (var i = 0; i < size; ++i)
{
var prob = rnd.Next(0, 100);
if (prob < 20)
{
var index1 = rnd.Next(0, size);
var index2 = rnd.Next(0, size);
while (index1 == index2)
{
index2 = rnd.Next(0, size);
}
var cost1 = _chromosomes[index1].Cost();
var cost2 = _chromosomes[index2].Cost();
if (IsBetter(cost1, cost2))
{
_chromosomes[index2].Copy(_chromosomes[index1]);
}
else
{
_chromosomes[index1].Copy(_chromosomes[index2]);
}
}
}
}
private bool IsBetter(long cost1, long cost2)
{
return _maximise ? cost1 > cost2 : cost1 < cost2;
}
}
}
Algorithm.cs
Used to apply genetic operators.
using System;
namespace LinearAssignmentProblem
{
public class Algorithm
{
private Population _population;
public Algorithm(Random rnd, int populationSize, int tasks, bool maximise)
{
_population = new Population(rnd, populationSize, tasks, maximise);
}
public void Run(Random rnd, CostMatrix costMatrix, int tasks)
{
var iteration = 1;
_population.Evaluate(costMatrix, iteration);
while (iteration < 1000)
{
_population.StoreBestSolution(tasks);
_population.Mutate(rnd);
_population.ApplyCrossover(rnd, tasks);
_population.SeedBestSolution(rnd);
_population.Evaluate(costMatrix, iteration);
_population.Selection(rnd);
iteration++;
}
}
}
}
Running the algorithm
As an example I use the same problem as demonstrated by Prof.G.Srinivasan, Department of Management Studies, IIT Madras on his YouTube video, shown at the following link:
https://www.youtube.com/watch?v=BUGIhEecipE
In the example lesson the optimal solution is determined to be 51 by assigning the following tasks:
Worker 1 -> Task 1 = 11 +
Worker 2 -> Task 3 = 7 +
Worker 3 -> Task 4 = 13 +
Worker 4 -> Task 2 = 10 +
Worker 5 -> Task 5 = 10
= 51 total.
This solution is obtained at the 15th iteration of the genetic algorithm as demonstrated by the screenshot below, albeit using zero-based indexing:
Here is a Visual Studio C++ implementation of the Hungarian algorithm available from here:
https://www.technical-recipes.com/Downloads/HungarianAlgorithm.zip


