Skip to content

Commit

Permalink
First GA version.
Browse files Browse the repository at this point in the history
  • Loading branch information
ojanacek committed Jan 13, 2016
1 parent 6ddf069 commit 3c3bd7f
Show file tree
Hide file tree
Showing 8 changed files with 406 additions and 0 deletions.
23 changes: 23 additions & 0 deletions KnapsackProblem.SimulatedEvolution/Chromosome.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Collections;
using System.Linq;

namespace KnapsackProblem.SimulatedEvolution
{
public sealed class Chromosome
{
public BitArray Gens { get; set; }

public int Weight { get; set; }
public int Fitness { get; set; }

public Chromosome(BitArray gens)
{
Gens = gens;
}

public override string ToString()
{
return $"Weight: {Weight}, Fitness: {Fitness}, Gens: {string.Join(" ", Gens.Cast<bool>().Select(b => b ? "1" : "0"))}";
}
}
}
128 changes: 128 additions & 0 deletions KnapsackProblem.SimulatedEvolution/GeneticAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using KnapsackProblem.Common;

namespace KnapsackProblem.SimulatedEvolution
{
public sealed class GeneticAlgorithm
{
private static readonly Random random = new Random();
private readonly GeneticAlgorithmArgs args;
private int chromosomeLength;

public GeneticAlgorithm(GeneticAlgorithmArgs args)
{
this.args = args;
}

public KnapsackSolution Solve(Knapsack knapsack)
{
chromosomeLength = knapsack.InstanceSize;
var population = CreatePopulation(args.PopulationSize);
int generation = 0;

while (true)
{
foreach (var chromosome in population)
{
EvaluateFitness(chromosome, knapsack);
}

PrintStatus(generation, population);
if (generation == args.MaxGenerations) // won't trigger if max generations is not set
break;

var newPopulation = new Chromosome[args.PopulationSize];
var breedingPool = new List<Chromosome>(args.PopulationSize);
int takeElites = 0;
if (args.ElitismDegree > 0)
{
takeElites = (int) (args.PopulationSize * args.ElitismDegree);
var elites = population.OrderByDescending(ch => ch.Fitness).Take(takeElites).ToArray();
Array.Copy(elites, newPopulation, elites.Length);
}

for (int i = takeElites; i < args.PopulationSize; i++)
{
breedingPool.Add(population.TakeRandom(args.TournamentSize, random).OrderByDescending(ch => ch.Fitness).First());
}

for (int i = takeElites; i < args.PopulationSize - 1; i++)
{
newPopulation[i] = Cross(breedingPool[i - takeElites], breedingPool[i + 1 - takeElites]);
}
newPopulation[args.PopulationSize - 1] = Cross(breedingPool[args.PopulationSize - 1 - takeElites], breedingPool[0]);
MutateRandomChromosome(newPopulation); // TODO: could be parametrized

population = newPopulation;
generation++;
}

var fittest = population.OrderBy(ch => ch.Fitness).First();
return new KnapsackSolution(fittest.Fitness, fittest.Gens, knapsack);
}

private Chromosome[] CreatePopulation(int populationSize)
{
var population = new Chromosome[populationSize];
var possibleChromosomesCount = (int)Math.Pow(2, chromosomeLength);
for (int i = 0; i < populationSize; i++)
{
int randomNumber = random.Next(1, possibleChromosomesCount);
var gens = new BitArray(chromosomeLength);
for (int j = 0; j < chromosomeLength; j++)
{
if ((randomNumber & (1 << j)) != 0)
gens.Set(j, true);
}
population[i] = new Chromosome(gens);
}
return population;
}

public static void EvaluateFitness(Chromosome chromosome, Knapsack knapsack)
{
for (int i = 0; i < chromosome.Gens.Count; i++)
{
if (chromosome.Gens.Get(i))
{
var matchingItem = knapsack.Items[i];
chromosome.Weight += matchingItem.Weight;
chromosome.Fitness += matchingItem.Price;
}

if (chromosome.Weight > knapsack.Capacity)
chromosome.Fitness = 0;
}
}

private static Chromosome Cross(Chromosome chromosome, Chromosome other)
{
// TODO: consider creating two offsprings instead of one
var gensCount = chromosome.Gens.Length;
int randomCutIndex = random.Next(1, gensCount);
var newGens = new BitArray(chromosome.Gens);
for (int i = randomCutIndex; i < gensCount; i++)
{
newGens.Set(i, other.Gens.Get(i));
}
return new Chromosome(newGens);
}

private void MutateRandomChromosome(Chromosome[] population)
{
int randomChromosomeIndex = random.Next(0, population.Length);
int randomGeneIndex = random.Next(0, chromosomeLength);
population[randomChromosomeIndex].Gens[randomGeneIndex] = !population[randomChromosomeIndex].Gens[randomGeneIndex];
}

private static void PrintStatus(int generation, Chromosome[] population)
{
int maxFitness = population.Max(ch => ch.Fitness);
int totalFitness = population.Sum(ch => ch.Fitness);
Console.WriteLine($"Generation {generation.PadRight(2)}: max fitness - {maxFitness.PadRight(8)}, total fitness - {totalFitness.PadRight(8)}");
}
}
}
18 changes: 18 additions & 0 deletions KnapsackProblem.SimulatedEvolution/GeneticAlgorithmArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace KnapsackProblem.SimulatedEvolution
{
public sealed class GeneticAlgorithmArgs
{
public int PopulationSize { get; }
public int MaxGenerations { get; }
public int TournamentSize { get; }
public double ElitismDegree { get; }

public GeneticAlgorithmArgs(int populationSize, int maxGenerations, int tournamentSize, double elitismDegree)
{
PopulationSize = populationSize;
MaxGenerations = maxGenerations;
TournamentSize = tournamentSize;
ElitismDegree = elitismDegree;
}
}
}
22 changes: 22 additions & 0 deletions KnapsackProblem.SimulatedEvolution/Helpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;

namespace KnapsackProblem.SimulatedEvolution
{
public static class Helpers
{
public static IEnumerable<T> TakeRandom<T>(this T[] array, int count, Random random)
{
for (int i = 0; i < count; i++)
{
int randomIndex = random.Next(0, array.Length);
yield return array[randomIndex];
}
}

public static string PadRight(this int number, int totalWidth)
{
return number.ToString().PadLeft(totalWidth, ' ');
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{866D9121-78DE-4D3B-8D77-6596A11BC5C0}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>KnapsackProblem.SimulatedEvolution</RootNamespace>
<AssemblyName>KnapsackProblem.SimulatedEvolution</AssemblyName>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="Chromosome.cs" />
<Compile Include="GeneticAlgorithm.cs" />
<Compile Include="GeneticAlgorithmArgs.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Helpers.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\KnapsackProblem.Common\KnapsackProblem.Common.csproj">
<Project>{d69e0ecc-70c5-41c3-abba-e4f385e6982a}</Project>
<Name>KnapsackProblem.Common</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
132 changes: 132 additions & 0 deletions KnapsackProblem.SimulatedEvolution/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
using System;
using System.Linq;
using System.Text;
using KnapsackProblem.Common;

namespace KnapsackProblem.SimulatedEvolution
{
delegate TResult ParseDelegate<in T1, T2, out TResult>(T1 input, out T2 output);

class Program
{
private const int OptionPadding = 14;

/* args:
[0] - a path to directory with testing files
[1] - how many files to use for testing
[2] - how many times repeat a single file
[3] - whether to save results
[4 ...] - GA arguments
*/
static void Main(string[] args)
{
var testFiles = Common.Helpers.LoadTestFiles(args);
if (testFiles == null)
{
Console.ReadLine();
return;
}

var genAlgArgs = ParseArguments(args);
if (genAlgArgs == null)
{
Console.ReadKey();
return;
}

var knapsacks = KnapsackLoader.LoadKnapsacks(testFiles.First(), 1);
var ga = new GeneticAlgorithm(genAlgArgs);
var result = ga.Solve(knapsacks.First());
Console.WriteLine("BEST PRICE FOUND " + result.BestPrice);

Console.ReadLine();
}

#region Generator arguments

static GeneticAlgorithmArgs ParseArguments(string[] args)
{
if (args.Length == 0 || args[0] == "-?" || args[0] == "/?")
{
PrintHelp();
return null;
}

try
{
int populationSize = ParseInt32Option(args, "p", true, 0, int.MaxValue);
int maxGenerations = ParseInt32Option(args, "g", true, 0, int.MaxValue);
int tournamentSize = ParseInt32Option(args, "t", true, 0, populationSize);
double elitismDegree = ParseDoubleOption(args, "e", true, 0, 1);

return new GeneticAlgorithmArgs(populationSize, maxGenerations, tournamentSize, elitismDegree);
}
catch { return null; }
}

static int ParseInt32Option(string[] args, string option, bool compulsory, int lowerLimit, int upperLimit, int defaultValue = 0)
{
return ParseOption(args, option, compulsory, int.TryParse, lowerLimit, upperLimit, defaultValue);
}

static double ParseDoubleOption(string[] args, string option, bool compulsory, double lowerLimit, double upperLimit, double defaultValue = 0)
{
return ParseOption(args, option, compulsory, double.TryParse, lowerLimit, upperLimit, defaultValue);
}

static T ParseOption<T>(string[] args, string option, bool compulsory, ParseDelegate<string, T, bool> parse, T lowerLimit, T upperLimit, T defaultValue) where T : IComparable<T>
{
option = "-" + option;
for (int i = 0; i < args.Length; i += 2)
{
if (string.Equals(args[i], option, StringComparison.InvariantCulture))
{
T value;
if (!parse(args[i + 1], out value))
{
Console.WriteLine($"{option} option value is not a valid {typeof(T).Name} number.");
throw new ArgumentException();
}

if (value.CompareTo(lowerLimit) == -1 || value.CompareTo(upperLimit) == 1)
{
Console.WriteLine(
$"{option} option value is limited to range from {lowerLimit} to {upperLimit}.");
throw new ArgumentException();
}

return value;
}
}

if (compulsory)
{
Console.WriteLine($"Option {option} is compulsory but it's missing.");
throw new ArgumentException();
}

return defaultValue;
}

static void PrintHelp()
{
var sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("Usage: -p size -g count -t size -e degree");
sb.AppendLine();
sb.AppendLine("Options:");
AppendOption("-p size", "Population size.", sb);
AppendOption("-g count", "Total # of generations before stopping.", sb);
AppendOption("-t size", "Tournament size.", sb);
AppendOption("-e degree", "Percents of a population's fittest pass to the next generation.", sb);
Console.WriteLine(sb.ToString());
}

static void AppendOption(string optionName, string description, StringBuilder sb)
{
sb.AppendLine($" {optionName.PadRight(OptionPadding)}{description}");
}

#endregion
}
}
Loading

0 comments on commit 3c3bd7f

Please sign in to comment.