using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using cAlgo; using Icarus.Utility; namespace Icarus.Optimiser.Core.Genetic { public class GeneticOptimiser where T : IChromosome { private readonly int m_NumberOfMutations; private readonly int m_NumberOfCrossovers; private readonly int m_NumberOfNewChromosomes; private readonly IChromosomeOperator m_ChromosomeOperator; private readonly IGeneticStudy m_GeneticStudy; private readonly int m_NumberOfCandidates; private readonly int m_NumberOfMostFitChromosomes; private List m_CurrentFittestChromosomes = new List(); private readonly HashSet m_TestedChromosomes = new HashSet(); private const int m_NumDiversificationGenerations = 5; private const int m_NumRefinementGenerations = 2; private const int m_NumGenerationsPerCycle = m_NumDiversificationGenerations + m_NumRefinementGenerations; public GeneticOptimiser(IGeneticStudy geneticStudy) { // A mutation is where a single chromosome is randomly changed, producing another single chromosome. // This number is how many mutations will be made from one generation to the next. m_NumberOfMutations = geneticStudy.NumberOfMutations; // A crossover is where two chromosomes are mixed together to produce another single chromosome. // This number is how many crossovers will be made from one generation to the next. m_NumberOfCrossovers = geneticStudy.NumberOfCrossovers; // This number is how many brand new chromosomes will be added to each generation m_NumberOfNewChromosomes = geneticStudy.NumberOfNewChromosomes; // The number of chromosomes in each generation m_NumberOfCandidates = m_NumberOfNewChromosomes + m_NumberOfMutations + m_NumberOfCrossovers * 2; if (m_NumberOfCandidates <= 0) { throw new Exception("Number of chromosomes is zero"); } // The number of most fit chromosomes to remember from all generations and evolve m_NumberOfMostFitChromosomes = m_NumberOfCandidates; m_ChromosomeOperator = geneticStudy.ChromosomeOperator; m_ChromosomeOperator.DiversityFactor = 1.0; m_GeneticStudy = geneticStudy; } public StudyResult Run() { int numChromosomesTested = 0; // Produce first generation var generation = CreateNewChromosomes(m_NumberOfCandidates).ToList(); generation.AddRange(m_ChromosomeOperator.GetSeeds()); int generationNumber = 0; T fittestChromosome = default(T); while (!m_GeneticStudy.HasStudyFinished(generationNumber, fittestChromosome)) { //Console.WriteLine("Running generation " + generationNumber); if (generationNumber % m_NumGenerationsPerCycle == 0) { Console.WriteLine("---"); Console.WriteLine("Entering DIVERSIFICATION phase for the next {0} generations...", m_NumDiversificationGenerations); m_ChromosomeOperator.DiversityFactor = 1.0; } if (generationNumber % m_NumGenerationsPerCycle == m_NumDiversificationGenerations) { Console.WriteLine("---"); Console.WriteLine("Entering REFINEMENT phase for the next {0} generations...", m_NumRefinementGenerations); m_ChromosomeOperator.DiversityFactor = 0.1; // Restrict randomness to 10% either side of known successful values } Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); RunGeneration(generation); stopwatch.Stop(); Debug.Print("Generation {0} finished in {1}s", generationNumber + 1, Math.Round((double)stopwatch.ElapsedMilliseconds / 1000)); numChromosomesTested += generation.Count(); m_CurrentFittestChromosomes = DetermineFittestChromosomes(m_CurrentFittestChromosomes, generation); fittestChromosome = GetFittestChromosome(); generation = ProduceNextGeneration(m_CurrentFittestChromosomes); generationNumber++; m_GeneticStudy.GenerationFinished(fittestChromosome, stopwatch.ElapsedMilliseconds, m_CurrentFittestChromosomes, generationNumber); } return new StudyResult { MostFitChromosome = GetFittestChromosome(), MostFitChromosomes = m_CurrentFittestChromosomes, NumberOfGenerations = generationNumber, NumberOfChromosomesTested = numChromosomesTested }; } private void RunGeneration(List generation) { m_GeneticStudy.RunGeneration(generation); if (generation.Any(x => !x.Fitness.HasValue)) { throw new Exception("Fitness was not set in the RunGeneration method for one or more chromosomes."); } } private List DetermineFittestChromosomes(List currentMostFit, List thisGeneration) { return currentMostFit.Concat(thisGeneration).OrderByDescending(x => x.Fitness.Value).Take(m_NumberOfMostFitChromosomes).ToList(); } private List CreateNewChromosomes(int number) { return BuildUntestedChromosomeList(() => m_ChromosomeOperator.GenerateNewChromosome(), number); } private List ProduceNextGeneration(List mostFitChromosomes) { List generation = new List(); // Create some brand new chromosomes for this generation so we are always trying out completely different ideas generation.AddRange(CreateNewChromosomes(m_NumberOfNewChromosomes)); // Pick a random set of most fit chromosomes to mutate var mutatedChromosomes = BuildUntestedChromosomeList(() => m_ChromosomeOperator.Mutate(mostFitChromosomes.Shuffle().First()), m_NumberOfMutations); generation.AddRange(mutatedChromosomes); // Pick a random set of most fit chromosomes to crossover var crossedOverChromosomes = BuildUntestedChromosomeList(() => m_ChromosomeOperator.CrossOver(mostFitChromosomes.Shuffle().First(), mostFitChromosomes.Shuffle().First()), m_NumberOfCrossovers); generation.AddRange(crossedOverChromosomes); return generation; } private List BuildUntestedChromosomeList(Func builder, int number) { List chromosomes = new List(); int attempts = 0; while (chromosomes.Count < number) { attempts++; if (attempts > number * 2) // We can't create enough unique chromosomes { return chromosomes; } var chromosome = builder(); if (chromosome.Fitness.HasValue) { throw new Exception("Fitness cannot be set on a newly created chromosome."); } if (!m_TestedChromosomes.Contains(chromosome)) { chromosomes.Add(chromosome); m_TestedChromosomes.Add(chromosome); } } return chromosomes; } private T GetFittestChromosome() { return m_CurrentFittestChromosomes.OrderByDescending(x => x.Fitness.Value).First(); } } }