Let's look at how simple it is to implement your own components & evolutionary algorithms.
If you want to have your problem solved by some of already implemented algorithms described above, you have to implement your own fitness function. Do not worry, it is a piece of cake!
struct MyFunction: EAFitnessFunctionProtocol {
typealias IndividualType = EADoubleIndividual
let dimension: Int
let range: ClosedRange<Double>
private let domainValidation: EASingleRangeDomainValidation<IndividualType>
init() {
self.dimension = 2
self.range = -5.0 ... 5.0
domainValidation = EASingleRangeDomainValidation(domain: EARangeDomain(range: range))
}
func evaluate(individual: IndividualType) -> Double {
var fitness: Double = 0.0
for x in individual.data {
fitness += x * x
}
return fitness
}
func getRandomIndividual(type: EADistributionType<IndividualType.DataType>) -> IndividualType {
var individual = IndividualType()
let distribution = EAUniformDistribution(range: range)
for _ in 0 ..< dimension {
let randomValue = distribution.random()
individual.data.append(randomValue)
}
individual = validateDomains(individual: individual)
individual.fitness = evaluate(individual: individual)
return individual
}
func validateDomains(individual: IndividualType) -> IndividualType {
return domainValidation.validate(individual: individual, fitnessFunction: self) ?? individual
}
}
EAFitnessFunctionProtocol
hasIndividualType
(which conforms toEAIndividualProtocol
) as a associated type (a generic parameter of protocol). You can define the associated type astypealias IndividualType = EADoubleIndividual
.func evaluate(individual: EADoubleIndividual) -> Double
.
- You should implement your problem in this method to calculate a fitness value of given individual.
func getRandomIndividual(type: EADistributionType<EADoubleIndividual.DataType>) -> EADoubleIndividual
- Generate a random individual in your search space (problem space).
func validateDomains(individual: EADoubleIndividual) -> EADoubleIndividual
.
- Use this method to validate domains of given individual, e.g. when your search space is limited or is discrete, you have to define boundaries of search space to prevent individuals from being in forbidden areas.
- That's it! You can use some of algorithms described above to have your problem solved.
As you can see, we used EADoubleIndividual
in our custom implementation of fitness function MySphereFunction
above. However, you can define your own individual to meet your expectations.
- If you only need different type of individual, you can use generic
EAIndividual<DataType>
class:
typealias MyIndividual = EAIndividual<String>
- If you need more sophisticated individual, you have to implement custom class/struct:
class MyIndividual: EAIndividualProtocol {
typealias DataType = Float
var position: [DataType] {
set { data = newValue }
get { return data }
}
var fitness: Double {
didSet {
if fitness < oldValue {
bestPosition = position
}
}
}
var data: [DataType]
var velocity: [DataType]
private(set) var bestPosition: [DataType]
public required init() {
bestPosition = []
velocity = []
data = []
fitness = Double.greatestFiniteMagnitude
}
}
EAIndividualProtocol
hasDataType
as a associated type (a generic parameter of protocol). You can define the associated type astypealias DataType = Float
.- You have to implement
fitness: Double
anddata: [DataType]
properties as required by protocol. - That's it! You can use your own individual in your fitness function or in other components.
Selection of individuals (parents) from given population.
struct MySelection: EASelectionProtocol {
typealias PopulationType = EAPopulation<EADoubleIndividual>
let isElitism: Bool
init(isElitism: Bool = false) {
self.isElitism = isElitism
}
func createNewPopulation(population: PopulationType) -> PopulationType? {
if let individual = population.bestIndividual, isElitism {
return PopulationType(individuals: [individual])
}
return nil
}
func prepare(population: PopulationType, context: EAContextProtocol?) {
}
func selectParents(population: PopulationType, count: Int, context: EAContextProtocol?) -> [PopulationType.IndividualType] {
let distribution = EAUniformDistribution(range: 0 ... population.individuals.count - 1)
let indexes = distribution.random(count: UInt(count))
return indexes.map { index -> PopulationType.IndividualType in
return population.individuals[index]
}
}
}
EASelectionProtocol
hasPopulationType
as a associated type (a generic parameter of protocol). You can define the associated type astypealias PopulationType = EAPopulation<EADoubleIndividual>
.func createNewPopulation(population: PopulationType) -> PopulationType?
.
- Create a new population from given population, e.g. appling elitism.
func prepare(population: PopulationType, context: EAContextProtocol?)
.
- Use this method for preprocessing current population.
func selectParents(population: PopulationType, count: Int, context: EAContextProtocol?) -> [PopulationType.IndividualType]
.
- Selection logic should be implemented here.
- That's it! You can use your own selection in some of already implemented algorithms.
Crossover two individuals (parents) to create offsprings.
struct MyCrossover: EACrossoverProtocol {
typealias IndividualType = EADoubleIndividual
let threshold: Double
init(threshold: Double) {
self.threshold = threshold
}
func cross(first: IndividualType, second: IndividualType) -> [IndividualType] {
let size = first.data.count
let uniformDistribution = EAUniformDistribution(range: 0.0 ... 1.0)
var firstIndividual = IndividualType()
var secondIndividual = IndividualType()
for dataIndex in 0 ..< size {
if uniformDistribution.random() <= 0.5 {
firstIndividual.data.append(first.data[dataIndex])
secondIndividual.data.append(second.data[dataIndex])
} else {
firstIndividual.data.append(second.data[dataIndex])
secondIndividual.data.append(first.data[dataIndex])
}
}
return [firstIndividual, secondIndividual]
}
}
EACrossoverProtocol
hasIndividualType
as a associated type (a generic parameter of protocol). You can define the associated type astypealias IndividualType = EADoubleIndividual
.- You have to implement
threshold: Double
property as required by protocol. func cross(first: IndividualType, second: IndividualType) -> [IndividualType]
.
- Crossover logic should be implemented here.
- That's it! You can use your own crossover in some of already implemented algorithms.
Mutate given individual.
struct MyMutation: EAMutationProtocol {
typealias IndividualType = EADoubleIndividual
let threshold: Double
let count: UInt
init(threshold: Double, count: UInt) {
self.threshold = threshold
self.count = count
}
mutating func prepare(context: EAContextProtocol?) {
}
func mutate(individual: IndividualType, context: EAContextProtocol?) -> IndividualType {
var individual = individual
let distribution = EAUniformDistribution(range: 0 ... individual.data.count - 1)
for _ in 0 ..< count {
let indexes = distribution.random(count: 2)
let temp = individual.data[indexes[0]]
individual.data[indexes[0]] = individual.data[indexes[1]]
individual.data[indexes[1]] = temp
}
return individual
}
}
EAMutationProtocol
hasIndividualType
as a associated type (a generic parameter of protocol). You can define the associated type astypealias IndividualType = EADoubleIndividual
.- You have to implement
threshold: Double
andcount: UInt
properties as required by protocol. func prepare(context: EAContextProtocol?)
.
- Use this method for preprocessing data if necessary.
func mutate(individual: IndividualType, context: EAContextProtocol?) -> IndividualType
.
- Mutation logic should be implemented here.
- That's it! You can use your own mutation in some of already implemented algorithms.
Create a recombinant from given individuals.
struct MyRecombination: EAEvolutionaryStrategyRecombinationProtocol {
typealias IndividualType = EADoubleIndividual
public init() {
}
func recombine(individuals: [IndividualType]) -> IndividualType {
let uniformDistribution = EAUniformDistribution(range: 0 ... individuals.count - 1)
var recombinant = IndividualType()
for dataIndex in 0 ..< (individuals.first?.data.count ?? 0) {
let randomIndex = uniformDistribution.random()
let data = individuals[randomIndex].data[dataIndex]
recombinant.data.append(data)
}
return recombinant
}
}
EAEvolutionaryStrategyRecombinationProtocol
hasIndividualType
as a associated type (a generic parameter of protocol). You can define the associated type astypealias IndividualType = EADoubleIndividual
.func recombine(individuals: [IndividualType]) -> IndividualType
.
- Recombination logic should be implemented here.
- That's it! You can use your own recombination in some of already implemented algorithms.
Mutate individuals to create a mutated individual from given active individual, global best individual and others individuals.
struct MyMutationStrategy: EADifferentialEvolutionMutationStrategyProtocol {
typealias IndividualType = EADoubleIndividual
let f: IndividualType.DataType
let λ: IndividualType.DataType
let parentsCount: Int = 3
init(f: IndividualType.DataType, λ: IndividualType.DataType) {
self.f = f
self.λ = λ
}
func mutate(activeIndividual: IndividualType, bestIndividual: IndividualType, individuals: [IndividualType], context: EADifferentialEvolutionContext) -> IndividualType {
var offspring = IndividualType()
for index in 0 ..< activeIndividual.data.count {
let value = individuals[2].data[index] + f * (individuals[0].data[index] - individuals[1].data[index])
offspring.data.append(value)
}
return offspring
}
}
EADifferentialEvolutionMutationStrategyProtocol
hasIndividualType
as a associated type (a generic parameter of protocol). You can define the associated type astypealias IndividualType = EADoubleIndividual
.- You have to implement
parentsCount: Int
property as required by protocol. func mutate(activeIndividual: IndividualType, bestIndividual: IndividualType, individuals: [IndividualType], context: EADifferentialEvolutionContext) -> IndividualType
.
- Mutation strategy logic should be implemented here.
- That's it! You can use your own mutation strategy in some of already implemented algorithms.
Population consists of array of individuals and keep the best individual in the population.
-
You can implement the
EAPopulationProtocol
protocol to create your population. -
You can create a subclass of
EAPopulation<IndividualType>
and override methods or added new ones.
final class MyPopulation: EAPopulation<EAParticleSwarmIndividual> {
override class func getRandomIndividual<FitnessFunction>(type: EADistributionType<Double>, fitnessFunction: FitnessFunction, context: EAContextProtocol?) -> EAParticleSwarmIndividual where IndividualType == FitnessFunction.IndividualType, FitnessFunction : EAFitnessFunctionProtocol {
let individual = super.getRandomIndividual(type: type, fitnessFunction: fitnessFunction, context: context)
guard let ctx = context as? EAParticleSwarmContext else {
return individual
}
let velocityMin = ctx.velocity.maximum * -1.0
let velocityMax = ctx.velocity.maximum
let uniformDistribution = EAUniformDistribution(range: velocityMin ... velocityMax)
for _ in 0 ..< fitnessFunction.dimension {
individual.velocity.append(uniformDistribution.random())
}
return individual
}
}
EAPopulation
orEAPopulationProtocol
hasIndividualType
as a associated type (a generic parameter of protocol). You can define the associated type as a generic parameter ofEAPopulation
ortypealias IndividualType = EAParticleSwarmIndividual
.- You have to implement
bestIndividual: IndividualType?
andindividuals: [IndividualType]
properties as required by protocol. static func getRandomIndividual<FitnessFunction: EAFitnessFunctionProtocol>(type: EADistributionType<IndividualType.DataType>, fitnessFunction: FitnessFunction, context: EAContextProtocol?) -> IndividualType where FitnessFunction.IndividualType == IndividualType
.
- Logic of creating random individual should be implemented here.
static func getRandomPopulation<FitnessFunction: EAFitnessFunctionProtocol>(type: EADistributionType<IndividualType.DataType>, fitnessFunction: FitnessFunction, size: UInt, context: EAContextProtocol?) -> Self where FitnessFunction.IndividualType == IndividualType
.
- Logic of creating random population should be implemented here.
- That's it! You can use your own population in your own evolutionary algorithm.
This is the component where you will use all components implemented above to create your own evolutionary algorithm.
- Parameters of an algorithm.
final class MyParameters<FitnessFunctionType: EAFitnessFunctionProtocol, SelectionType: EASelectionProtocol, MutationStrategyType: EADifferentialEvolutionMutationStrategyProtocol, CrossoverType: EACrossoverProtocol>: EAAlgorithmParameters<EADifferentialEvolution<FitnessFunctionType, SelectionType, MutationStrategyType, CrossoverType>, EAPopulation<FitnessFunctionType.IndividualType>> where FitnessFunctionType.IndividualType == CrossoverType.IndividualType, EAPopulation<FitnessFunctionType.IndividualType> == SelectionType.PopulationType, FitnessFunctionType.IndividualType == MutationStrategyType.IndividualType {
public var np: UInt {
populationCount
}
var selection: SelectionType
let mutationStrategy: MutationStrategyType
let crossover: CrossoverType
init(populationCount: UInt, generationsCount: UInt, selection: SelectionType, mutationStrategy: MutationStrategyType, crossover: CrossoverType, fitnessFunction: FitnessFunctionType, output: EAAlgorithmParametersOutput = .defaultOutput, delegate: EAAlgorithmDelegate<EADifferentialEvolution<FitnessFunctionType, SelectionType, MutationStrategyType, CrossoverType>, EAPopulation<FitnessFunctionType.IndividualType>>? = nil) throws {
self.selection = selection
self.mutationStrategy = mutationStrategy
self.crossover = crossover
try super.init(populationCount: populationCount, generationsCount: generationsCount, fitnessFunction: fitnessFunction, output: output, delegate: delegate)
}
}
1.1 Use EAAlgorithmParameters
to create a subclass and add others parameters.
1.2 EAAlgorithmParameters
has AlgorithmType
and PopulationType
as a generic parameter. Define these generic parameters in the signature of your subclass, e.g. EAAlgorithmParameters<EADifferentialEvolution<FitnessFunctionType, SelectionType, MutationStrategyType, CrossoverType>, EAPopulation<FitnessFunctionType.IndividualType>>
.
1.3 Define generic constraints to assure that all generic parameters have the same PopulationType
and IndividualType
.
1.4 Create a constructor and call super constuctor.
- An Algorithm
final class MyAlgorithm<FitnessFunctionType: EAFitnessFunctionProtocol, SelectionType: EASelectionProtocol, MutationStrategyType: EADifferentialEvolutionMutationStrategyProtocol, CrossoverType: EACrossoverProtocol>: EAAlgorithmProtocol where EAPopulation<FitnessFunctionType.IndividualType> == SelectionType.PopulationType, FitnessFunctionType.IndividualType == MutationStrategyType.IndividualType, FitnessFunctionType.IndividualType == CrossoverType.IndividualType {
let parameters: EADifferentialEvolutionParameters<FitnessFunctionType, SelectionType, MutationStrategyType, CrossoverType>
private let uniformUnifiedDistribution = EAUniformDistribution(range: 0.0 ... 1.0)
init(parameters: EADifferentialEvolutionParameters<FitnessFunctionType, SelectionType, MutationStrategyType, CrossoverType>) {
self.parameters = parameters
}
func run() -> EAAlgorithmResult<EAPopulation<FitnessFunctionType.IndividualType>> {
let context = EADifferentialEvolutionContext()
var currentPopulation = PopulationType.getRandomPopulation(type: .uniform, fitnessFunction: parameters.fitnessFunction, size: parameters.populationCount, context: context)
let result = EAAlgorithmResult(population: currentPopulation)
for generationIndex in 0 ..< parameters.generationsCount - 1 {
let population = PopulationType(individuals: [])
parameters.selection.prepare(population: currentPopulation, context: context)
for individual in currentPopulation.individuals {
let parents = parameters.selection.selectParents(population: currentPopulation, count: parameters.mutationStrategy.parentsCount, context: context)
var offspring = parameters.mutationStrategy.mutate(activeIndividual: individual, bestIndividual: currentPopulation.bestIndividual!, individuals: parents, context: context)
offspring = parameters.crossover.cross(first: individual, second: offspring).first!
offspring = parameters.fitnessFunction.validateDomains(individual: offspring)
offspring.fitness = parameters.fitnessFunction.evaluate(individual: offspring)
if offspring.fitness < individual.fitness {
population.append(individual: offspring)
} else {
population.append(individual: individual)
}
}
currentPopulation = population
result.append(population: population, keepBestOnly: !parameters.output.saveProgress)
parameters.delegate?.didFinishGeneration?(self, generationIndex, currentPopulation)
}
return result
}
}
2.1 Define all generic parameters as you did in 1. step (If you want to avoid this, you do not have to separate parameters of algorithm and algorithm itself. I do, because it keeps logic of algorithm clean).
2.2 func run() -> EAAlgorithmResult<EAPopulation<FitnessFunctionType.IndividualType>>
- Implementation logic of an algorithm should be implemented here. 2.3 That's it! You have your own generic evolutionary algorithm and you can use it.