More on the Generational Evolution Engine
The generational evolution engine is a simple evolution engine that evolves things in generations. The basic algorithm is as follows:
- Create initial population
- Evaluate initial population
- while termination condition not met
- select parents of next generation
- evolve children from parents
- evaluate children
The basics of the generational evolution engine are defined as follows:
public struct GenerationalEvolutionEngine<CF: CandidateFactory>: EvolutionEngine { public typealias EvolveType = CF.ProduceType // Some stuff public init(candidateFactory: CF, evolutionOperator: @escaping EvolutionOperator<EvolveType>, fitnessEvaluator: @escaping (EvolveType, [EvolveType]) -> Double, isNatural: Bool = true, selectionStrategy: @escaping ([EvaluatedCandidate<EvolveType>], Bool, Int, PRNG) -> [EvolveType], rng: PRNG) { // Some stuff } // implementation }
The rest of this post is based on the Candidate Factory section of the Watchmaker user manual.
Here is the CandidateFactory
protocol
public protocol CandidateFactory { /// The type of thing to produce associatedtype ProduceType /// Generate a single random candidate /// /// - Parameter rng: The random number generator to use. /// - Returns: A random candidate func generateRandomCandidate(rng: PRNG) -> ProduceType }
I could have just made it a function, but in most cases you don’t want one candidate but a whole population of candidates, and there is often context required that affects how the candidates are generated. Therefore, it seemed cleaner to make it a protocol with an extension for generating a population.
public extension CandidateFactory { /// Create a random population possibly using a seed population. If there are /// more seeds than we need for the whole population, use the first `count` of them. /// /// - Parameters: /// - count: Number of individuals in the population. /// - seeds: Preselected seed individuals /// - rng: Random number generator /// - Returns: An array of `ProduceType` to act as the population. public func generateInitialPopulation(count: Int, seeds: [ProduceType] = [], rng: PRNG) -> [ProduceType] { guard seeds.count < count else { return Array(seeds[0 ..< count]) } var ret: [ProduceType] = seeds while ret.count < count { ret.append(generateRandomCandidate(rng: rng)) } return ret } }
Note that the candidate factory produces an array of ProduceType
and that the generational evolution engine aliases it to its EvolveType
. This means that the Swift compiler can infer pretty much everything about the evolution engine’s type from the first parameter of its initialiser.
That’s all there is to it as far as the candidate factory is concerned. Next we will talk about evolution operators.