public CandidateSolution <T, S> FindBestSolution() { // keep track of best overall, average fitness scores float bestFitnessScoreAllTime = (IsLowerFitnessBetter ? float.MaxValue : float.MinValue); float bestAverageFitnessScore = (IsLowerFitnessBetter ? float.MaxValue : float.MinValue); CandidateSolution <T, S> bestTreeAllTime = null; int bestSolutionGenerationNumber = 0, bestAverageFitnessGenerationNumber = 0; // elitism int numElitesToAdd = (int)(ElitismRate * PopulationSize); // depending on whether elitism is used, or the selection type, we may need to sort candidates by fitness (which is slower) bool needToSortByFitness = SelectionStyle == SelectionStyle.RouletteWheel || SelectionStyle == SelectionStyle.Ranked || ElitismRate > 0; Stopwatch timer = new Stopwatch(); // create an initial population of random trees, passing the possible functions, consts, and variables for (int p = 0; p < PopulationSize; p++) { CandidateSolution <T, S> tree = new CandidateSolution <T, S>(primitiveSet, RandomTreeMinDepth, RandomTreeMaxDepth); tree.CreateRandom(); currentGeneration.Add(tree); } // loop over generations int currentGenerationNumber = 0; while (true) { timer.Restart(); // for each tree, find and store the fitness score // multithread the fitness evaluation Parallel.ForEach(currentGeneration, (candidate) => { // calc the fitness by calling the user-supplied function via the delegate float fitness = myFitnessFunction(candidate); candidate.Fitness = fitness; }); // single-threaded approach, for debugging //foreach (var candidate in currentGeneration) // candidate.Fitness = myFitnessFunction(candidate); // now check if we have a new best float bestFitnessScoreThisGeneration = (IsLowerFitnessBetter ? float.MaxValue : float.MinValue); CandidateSolution <T, S> bestTreeThisGeneration = null; float totalFitness = 0; foreach (var tree in currentGeneration) { totalFitness += tree.Fitness; // find best of this generation, update best all-time if needed bool isBestThisGeneration = false; if (IsLowerFitnessBetter) { isBestThisGeneration = tree.Fitness < bestFitnessScoreThisGeneration; } else { isBestThisGeneration = tree.Fitness > bestFitnessScoreThisGeneration; } if (isBestThisGeneration) { bestFitnessScoreThisGeneration = tree.Fitness; bestTreeThisGeneration = tree; bool isBestEver = false; if (IsLowerFitnessBetter) { isBestEver = bestFitnessScoreThisGeneration < bestFitnessScoreAllTime; } else { isBestEver = bestFitnessScoreThisGeneration > bestFitnessScoreAllTime; } if (isBestEver) { bestFitnessScoreAllTime = bestFitnessScoreThisGeneration; bestTreeAllTime = tree.Clone(); bestSolutionGenerationNumber = currentGenerationNumber; } } } // determine average fitness and store if it's all-time best float averageFitness = totalFitness / PopulationSize; if (IsLowerFitnessBetter) { if (averageFitness < bestAverageFitnessScore) { bestAverageFitnessGenerationNumber = currentGenerationNumber; bestAverageFitnessScore = averageFitness; } } else { if (averageFitness > bestAverageFitnessScore) { bestAverageFitnessGenerationNumber = currentGenerationNumber; bestAverageFitnessScore = averageFitness; } } // report progress back to the user, and allow them to terminate the loop EngineProgress progress = new EngineProgress() { GenerationNumber = currentGenerationNumber, AvgFitnessThisGen = averageFitness, BestFitnessThisGen = bestFitnessScoreThisGeneration, BestFitnessSoFar = bestFitnessScoreAllTime, TimeForGeneration = timer.Elapsed }; // there are two forms here, and if they aren't defined, this code just drops through bool keepGoing = true; if (myProgressFunction != null) { myProgressFunction(progress); } if (myProgressWithCandidateFunction != null) { keepGoing = myProgressWithCandidateFunction(progress, bestTreeThisGeneration); } if (!keepGoing) { break; // user signalled to end looping } // termination conditions if (currentGenerationNumber >= MinGenerations) { // exit the loop if we're not making any progress in our average fitness score or our overall best score if (((currentGenerationNumber - bestAverageFitnessGenerationNumber) >= StagnantGenerationLimit) && ((currentGenerationNumber - bestSolutionGenerationNumber) >= StagnantGenerationLimit)) { break; } // maxed out? if (currentGenerationNumber >= MaxGenerations) { break; } } // we may need to sort the current generation by fitness, depending on SelectionStyle if (needToSortByFitness) { if (IsLowerFitnessBetter) { currentGeneration = currentGeneration.OrderBy(c => c.Fitness).ToList(); } else { currentGeneration = currentGeneration.OrderByDescending(c => c.Fitness).ToList(); } } // depending on the SelectionStyle, we may need to adjust all candidate's fitness scores AdjustFitnessScores(currentGeneration); // Start building the next generation List <CandidateSolution <T, S> > nextGeneration = new List <CandidateSolution <T, S> >(); // Elitism var theBest = currentGeneration.Take(numElitesToAdd); foreach (var peakPerformer in theBest) { nextGeneration.Add(peakPerformer); } // now create a new generation using fitness scores for selection, and crossover and mutation while (nextGeneration.Count < PopulationSize) { // select parents CandidateSolution <T, S> parent1 = null, parent2 = null; switch (SelectionStyle) { case SelectionStyle.Tourney: parent1 = TournamentSelectParent(); parent2 = TournamentSelectParent(); break; case SelectionStyle.RouletteWheel: case SelectionStyle.Ranked: parent1 = RouletteSelectParent(); parent2 = RouletteSelectParent(); break; } // cross them over to generate two new children CandidateSolution <T, S> child1, child2; CrossOverParents(parent1, parent2, out child1, out child2); // Mutation if (Randomizer.GetFloatFromZeroToOne() < MutationRate) { child1.Mutate(); } if (Randomizer.GetFloatFromZeroToOne() < MutationRate) { child2.Mutate(); } // then add to the new generation nextGeneration.Add(child1); nextGeneration.Add(child2); } // move to the next generation currentGeneration = nextGeneration; currentGenerationNumber++; } bestTreeAllTime.Root.SetCandidateRef(bestTreeAllTime); return(bestTreeAllTime); }