/// <summary> /// Progresses the optimization by evolving the current population. /// </summary> /// <returns>The number of generations thus far.</returns> public int NewGeneration() { if (population == null) { throw new InvalidOperationException("Cannot generate a next" + " generation without prior call to StartEvolution!"); } List <Individual> newPopulation = new List <Individual>(); generationCount++; WeightedSampler <Individual> sampler = new WeightedSampler <Individual>(); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); // Check the fitness values of the current generation. foreach (Individual individual in population) { if (individual.Fitness < 0) { throw new ArgumentOutOfRangeException("solutionFitness function", "Negative fitness values are not allowed! Use 0 fitness " + "for solutions that should not reproduce."); } if (individual.Fitness > bestSolution.Fitness) { bestSolution = new Individual(individual.DNA, individual.Fitness); } maxFitness = Math.Max(individual.Fitness, maxFitness); minFitness = Math.Min(individual.Fitness, minFitness); } double averageHealth = 0; double averageBitsSet = 0; double averageAge = 0; int acceptedTotal = 0; int acceptedWorse = 0; int purgedIndividuals = 0; // Sort the population by fitness. population = population.OrderBy(ind => ind.Fitness).ToArray(); int index = 0; foreach (Individual individual in population) { index++; /// Max and min fitness might have changed, so we need to /// normalize the fitnesses again. individual.normalizedFitness = normalizeFitness(individual.Fitness); averageHealth += individual.normalizedFitness; averageBitsSet += SetBits(individual.DNA); // Survival of the fittest (population was ordered by fitness above) if (index < 0.5 * populationSize) { // Could be slightly more concise, I know. purgedIndividuals++; continue; } /// This seems to have a good effect on convergence speed. /// By only allowing solutions that survived a round of culling /// to procreate, the solution quality is kept high. if (individual.Age >= 1) { sampler.AddEntry(individual, individual.normalizedFitness); } averageAge += individual.Age; individual.Age++; // Simulated annealing Individual temp = individual; Individual mutation = spawnIndividual(mutateDNA(individual.DNA)); mutation.Age = individual.Age - 1; // TODO: Investigate. if (acceptNewState(individual, mutation)) { acceptedTotal++; if (mutation.Fitness < individual.Fitness) { acceptedWorse++; } temp = mutation; } newPopulation.Add(temp); } /*if (purgedIndividuals == 0) * minFitness = minCurrentFitness;*/ stopwatch.Stop(); Console.Write("Evaluation time for " + generationCount + " : "); Console.WriteLine(stopwatch.ElapsedMilliseconds + " ms"); Console.WriteLine("Temperature: " + temperature); Console.WriteLine("Average health: " + averageHealth / populationSize); Console.WriteLine("Average bits set: " + averageBitsSet / populationSize); Console.WriteLine("Average age: " + averageAge / populationSize); Console.WriteLine("Accepted new states (all/worse): " + acceptedTotal + "/" + acceptedWorse); //Console.WriteLine("Purged individuals: " + purgedIndividuals + "/" + populationSize); Console.WriteLine("Sampler entries: " + sampler.EntryCount); stopwatch.Restart(); if (!sampler.CanSample) { // This is actually a pretty serious problem. population = createPopulation(); Console.WriteLine("Entire population was infertile (Generation " + generationCount + ")."); //Debug.Fail("Population went extinct, not good..."); return(generationCount); } // Breed population and apply random mutations. int dnaResets = 0; // Replace purged individuals for (int i = 0; i < purgedIndividuals; i++) { BitArray parent1 = sampler.RandomSample().DNA; BitArray parent2 = sampler.RandomSample().DNA; BitArray newDNA = combineIndividualsDNA(parent1, parent2); newPopulation.Add(spawnIndividual(newDNA)); } population = newPopulation.ToArray(); // Yeah, I know, out of thin air. temperature *= annealingFactor; stopwatch.Stop(); Console.WriteLine("Best value so far: " + (1500 - bestSolution.Fitness)); Console.WriteLine("------------------"); Console.Out.Flush(); return(generationCount); }
/// <summary> /// Progresses the optimization by evolving the current population. /// </summary> /// <returns>The number of generations thus far.</returns> public int NewGeneration() { if (population == null) { throw new InvalidOperationException("Cannot generate a next" + " generation without prior call to InitializeEvolution!"); } Individual[] newPopulation = new Individual[populationSize]; int newPopIndex = 0; generationCount++; WeightedSampler <Individual> sampler = new WeightedSampler <Individual>(); #if DEBUG Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); double averageHealth = 0; double averageBitsSet = 0; double averageAge = 0; int acceptedTotal = 0; int acceptedWorse = 0; #endif // Sort the population by fitness. population = population.OrderBy(ind => ind.Fitness).ToArray(); int index = 0; foreach (Individual individual in population) { index++; #if DEBUG averageHealth += 1500 - individual.Fitness; averageBitsSet += SetBits(individual.DNA); #endif // Survival of the fittest (population was ordered by fitness above) if (index < 0.5 * populationSize) { continue; } /// This seems to have a good effect on convergence speed. /// By only allowing solutions that survived a round of culling /// to procreate, the solution quality is kept high. if (individual.Age >= 1) { sampler.AddEntry(individual, individual.Fitness); } #if DEBUG averageAge += individual.Age; #endif individual.Age++; newPopulation[newPopIndex] = individual; newPopIndex++; } //for (int i = 0; i < newPopIndex; i++) Parallel.For(0, newPopIndex, i => { Individual temp = newPopulation[i]; Individual mutation = spawnIndividual(mutateDNA(temp.DNA)); // Lowering the age here would lead to faster convergence but would // make the population go extinct several times. mutation.Age = temp.Age; // Mutations have a chance to be rejected based on the fitness loss // relative to the non-mutated individual. See explanation above. if (acceptNewState(temp, mutation)) { #if DEBUG // If you want to measure these for debugging purposes, remove the // parallelization of this loop. //acceptedTotal++; //if (mutation.Fitness < temp.Fitness) // acceptedWorse++; #endif temp = mutation; } newPopulation[i] = temp; }); #if DEBUG stopwatch.Stop(); //Console.Write("Evaluation time for " + generationCount + " : "); //Console.WriteLine(stopwatch.ElapsedMilliseconds + " ms"); //Console.WriteLine("Average health: " + averageHealth / populationSize); //Console.WriteLine("Average bits set: " + averageBitsSet / populationSize); //Console.WriteLine("Average age: " + averageAge / populationSize); //Console.WriteLine("Accepted new states (all/worse): " + acceptedTotal + "/" + acceptedWorse); //Console.WriteLine("Sampler entries: " + sampler.EntryCount); stopwatch.Restart(); #endif if (!sampler.CanSample) { // This is actually a pretty serious problem. population = createPopulation(); Console.WriteLine("Entire population was infertile (Generation " + generationCount + ")."); //Debug.Fail("Population went extinct, not good..."); return(generationCount); } // Replace purged individuals //for (int i = newPopIndex; i < populationSize; i++) Parallel.For(newPopIndex, populationSize, i => { BitArray parent1 = sampler.RandomSample().DNA; BitArray parent2 = sampler.RandomSample().DNA; BitArray newDNA = combineIndividualsDNA(parent1, parent2); newPopulation[i] = spawnIndividual(newDNA); }); population = newPopulation; // Doing this at the end so the last generation has a use. updateBestSolution(); #if DEBUG stopwatch.Stop(); //Console.WriteLine("Best value so far: " + (1500 - bestSolution.Fitness)); //Console.WriteLine("------------------"); //Console.Out.Flush(); #endif return(generationCount); }