Skip to content

Commit

Permalink
Publicizing API to support use as a package.
Browse files Browse the repository at this point in the history
  • Loading branch information
sgonzalez committed Nov 21, 2019
1 parent 4cdf0d9 commit 6154861
Show file tree
Hide file tree
Showing 15 changed files with 157 additions and 87 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

**SwiftGenetics** is a genetic algorithm library that has been engineered from-scratch to be highly extensible and composable, by abstracting away different pieces of functionality, while providing concrete implementations of certain use cases, such as tree-based genomes. **SwiftGenetics** is written in pure Swift, and makes proper use of functional programming and Swift's wonderful type system. This project is provided under the MIT License (see the `LICENSE` file for more info).

**SwiftGenetics** can be added to your project as a dependency with the Swift Package Manager.

## Functionality

**SwiftGenetics** provides an abstracted base for genetic algorithms, intended to support different representations and genetic operators.
Expand Down
39 changes: 30 additions & 9 deletions Sources/Clades/LivingStrings/LivingStringEnvironment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,40 @@
import Foundation

/// The environment configuration for an ecosystem of living strings.
struct LivingStringEnvironment: GeneticEnvironment {
public struct LivingStringEnvironment: GeneticEnvironment {

// MARK: Genetic Constants

var populationSize: Int
var selectionMethod: SelectionMethod
var selectableProportion: Double
var mutationRate: Double
var crossoverRate: Double
var numberOfElites: Int
var numberOfEliteCopies: Int
var parameters: [String : Any]
public var populationSize: Int
public var selectionMethod: SelectionMethod
public var selectableProportion: Double
public var mutationRate: Double
public var crossoverRate: Double
public var numberOfElites: Int
public var numberOfEliteCopies: Int
public var parameters: [String : Any]

// MARK: Implementation-Specific Constants


/// Creates a new environment.
public init(
populationSize: Int,
selectionMethod: SelectionMethod,
selectableProportion: Double,
mutationRate: Double,
crossoverRate: Double,
numberOfElites: Int,
numberOfEliteCopies: Int,
parameters: [String : Any]
) {
self.populationSize = populationSize
self.selectionMethod = selectionMethod
self.selectableProportion = selectableProportion
self.mutationRate = mutationRate
self.crossoverRate = crossoverRate
self.numberOfElites = numberOfElites
self.numberOfEliteCopies = numberOfEliteCopies
self.parameters = parameters
}
}
27 changes: 16 additions & 11 deletions Sources/Clades/LivingStrings/LivingStringGenome.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,26 @@
import Foundation

/// An evolvable sequence of genes.
struct LivingStringGenome<GeneType: Gene>: Genome where GeneType.Environment == LivingStringEnvironment {
public struct LivingStringGenome<GeneType: Gene>: Genome where GeneType.Environment == LivingStringEnvironment {

typealias RealGene = GeneType
typealias Environment = LivingStringEnvironment
public typealias RealGene = GeneType
public typealias Environment = LivingStringEnvironment

/// The actual sequence of genes.
var genes: [RealGene]
public var genes: [RealGene]

mutating func mutate(rate: Double, environment: Environment) {
/// Creates a new genome with the given array of genes.
public init(genes: [RealGene]) {
self.genes = genes
}

mutating public func mutate(rate: Double, environment: Environment) {
for i in 0..<genes.count {
genes[i].mutate(rate: rate, environment: environment)
}
}

func crossover(with partner: LivingStringGenome, rate: Double, environment: Environment) -> (LivingStringGenome, LivingStringGenome) {
public func crossover(with partner: LivingStringGenome, rate: Double, environment: Environment) -> (LivingStringGenome, LivingStringGenome) {
guard Double.fastRandomUniform() < rate else { return (self, partner) }
guard partner.genes.count > 1 && self.genes.count > 1 else { return (self, partner) }

Expand Down Expand Up @@ -51,21 +56,21 @@ struct LivingStringGenome<GeneType: Gene>: Genome where GeneType.Environment ==
}

extension LivingStringGenome: RawRepresentable where RealGene: Hashable {
typealias RawValue = [RealGene]
var rawValue: RawValue { return genes }
init?(rawValue: RawValue) {
public typealias RawValue = [RealGene]
public var rawValue: RawValue { return genes }
public init?(rawValue: RawValue) {
self = LivingStringGenome.init(genes: rawValue)
}
}

extension LivingStringGenome: Equatable where GeneType: Equatable {
static func == (lhs: LivingStringGenome<GeneType>, rhs: LivingStringGenome<GeneType>) -> Bool {
public static func == (lhs: LivingStringGenome<GeneType>, rhs: LivingStringGenome<GeneType>) -> Bool {
return lhs.genes == rhs.genes
}
}

extension LivingStringGenome: Hashable where GeneType: Hashable {
func hash(into hasher: inout Hasher) {
public func hash(into hasher: inout Hasher) {
hasher.combine(genes)
}
}
52 changes: 40 additions & 12 deletions Sources/Clades/LivingTrees/LivingTreeEnvironment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,54 @@
import Foundation

/// The environment configuration for an ecosystem of living trees.
struct LivingTreeEnvironment: GeneticEnvironment {
public struct LivingTreeEnvironment: GeneticEnvironment {

// MARK: Genetic Constants

var populationSize: Int
var selectionMethod: SelectionMethod
var selectableProportion: Double
var mutationRate: Double
var crossoverRate: Double
var numberOfElites: Int
var numberOfEliteCopies: Int
var parameters: [String : Any]
public var populationSize: Int
public var selectionMethod: SelectionMethod
public var selectableProportion: Double
public var mutationRate: Double
public var crossoverRate: Double
public var numberOfElites: Int
public var numberOfEliteCopies: Int
public var parameters: [String : Any]

// MARK: Implementation-Specific Constants

/// The maximum amount a scalar can drift during a mutation.
var scalarMutationMagnitude: Int
public var scalarMutationMagnitude: Int

/// How often deletion mutations take place, between 0 and 1.
var structuralMutationDeletionRate: Double
public var structuralMutationDeletionRate: Double
/// How often addition mutations take place, between 0 and 1.
var structuralMutationAdditionRate: Double
public var structuralMutationAdditionRate: Double


/// Creates a new environment.
public init(
populationSize: Int,
selectionMethod: SelectionMethod,
selectableProportion: Double,
mutationRate: Double,
crossoverRate: Double,
numberOfElites: Int,
numberOfEliteCopies: Int,
parameters: [String : Any],
scalarMutationMagnitude: Int,
structuralMutationDeletionRate: Double,
structuralMutationAdditionRate: Double
) {
self.populationSize = populationSize
self.selectionMethod = selectionMethod
self.selectableProportion = selectableProportion
self.mutationRate = mutationRate
self.crossoverRate = crossoverRate
self.numberOfElites = numberOfElites
self.numberOfEliteCopies = numberOfEliteCopies
self.parameters = parameters
self.scalarMutationMagnitude = scalarMutationMagnitude
self.structuralMutationAdditionRate = structuralMutationAdditionRate
self.structuralMutationDeletionRate = structuralMutationDeletionRate
}
}
23 changes: 14 additions & 9 deletions Sources/Clades/Primitive Genes/ContinuousGene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,35 @@
import Foundation

/// The types of mutations that can be performed on a real-valued gene.
enum ContinuousMutationType {
public enum ContinuousMutationType {
/// Add a sample from a uniform distribution, centered at zero, to the value.
case uniform
/// Add a sample from a Gaussian distribution, centered at zero, to the value.
case gaussian
}

/// Keys to the environment's parameters dictionary that a real-valued gene uses.
enum ContinuousEnvironmentParameter: String {
public enum ContinuousEnvironmentParameter: String {
/// How large should mutations be.
case mutationSize // Double
/// The type of mutations that are performed.
case mutationType // MutationType
}

/// Represents a single continuous value that can be evolved.
struct ContinuousGene<R: FloatingPoint, E: GeneticEnvironment>: Gene, Equatable, Hashable {
typealias Environment = E
typealias Param = ContinuousEnvironmentParameter
public struct ContinuousGene<R: FloatingPoint, E: GeneticEnvironment>: Gene, Equatable, Hashable {
public typealias Environment = E
public typealias Param = ContinuousEnvironmentParameter

/// The gene's value.
var value: R
public var value: R

mutating func mutate(rate: Double, environment: ContinuousGene<R, E>.Environment) {
/// Creates a new gene with the given value.
public init(value: R) {
self.value = value
}

mutating public func mutate(rate: Double, environment: ContinuousGene<R, E>.Environment) {
guard Double.fastRandomUniform() < rate else { return }

// Get environmental mutation parameters.
Expand Down Expand Up @@ -67,11 +72,11 @@ struct ContinuousGene<R: FloatingPoint, E: GeneticEnvironment>: Gene, Equatable,
}
}

static func == (lhs: ContinuousGene, rhs: ContinuousGene) -> Bool {
public static func == (lhs: ContinuousGene, rhs: ContinuousGene) -> Bool {
return lhs.value == rhs.value // TODO: maybe this could cause issues, bad to compare IEEE float equality...
}

func hash(into hasher: inout Hasher) {
public func hash(into hasher: inout Hasher) {
hasher.combine(value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,25 @@
import Foundation

#if os(OSX) || os(iOS) // Linux does not have Dispatch
// TODO: test out on Linux.

/// Encapsulates a generic genetic algorithm that performs synchronous fitness
/// evaluations concurrently. The fitness evaluator needs to be thread-safe.
final class ConcurrentSynchronousEvaluationGA<Eval: SynchronousFitnessEvaluator, LogDelegate: EvolutionLoggingDelegate> : EvolutionWrapper where Eval.G == LogDelegate.G {
final public class ConcurrentSynchronousEvaluationGA<Eval: SynchronousFitnessEvaluator, LogDelegate: EvolutionLoggingDelegate> : EvolutionWrapper where Eval.G == LogDelegate.G {

var fitnessEvaluator: Eval
var afterEachEpochFns = [(Int) -> ()]()
public var fitnessEvaluator: Eval
public var afterEachEpochFns = [(Int) -> ()]()

/// A delegate for logging information from the GA.
var loggingDelegate: LogDelegate

/// Creates a new evolution wrapper.
init(fitnessEvaluator: Eval, loggingDelegate: LogDelegate) {
public init(fitnessEvaluator: Eval, loggingDelegate: LogDelegate) {
self.fitnessEvaluator = fitnessEvaluator
self.loggingDelegate = loggingDelegate
}

func evolve(population: Population<Eval.G>, configuration: EvolutionAlgorithmConfiguration) {
public func evolve(population: Population<Eval.G>, configuration: EvolutionAlgorithmConfiguration) {
for i in 0..<configuration.maxEpochs {
// Log start of epoch.
loggingDelegate.evolutionStartingEpoch(i)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Foundation

/// Implemented by types that can receive log events for a GA.
protocol EvolutionLoggingDelegate {
public protocol EvolutionLoggingDelegate {
associatedtype G: Genome

/// Called at the beginning of an epoch.
Expand Down
16 changes: 11 additions & 5 deletions Sources/Evolution/Abstractions/EvolutionWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,20 @@ import Foundation

/// The high-level configuration for a GA. Hyperparameters that are too broad to be considered part of a
/// `GeneticEnvironment` are included here.
struct EvolutionAlgorithmConfiguration {
let maxEpochs: Int
let algorithmType: EvolutionAlgorithmType
public struct EvolutionAlgorithmConfiguration {
public let maxEpochs: Int
public let algorithmType: EvolutionAlgorithmType

/// Creates a new EA configruation.
public init(maxEpochs: Int, algorithmType: EvolutionAlgorithmType) {
self.maxEpochs = maxEpochs
self.algorithmType = algorithmType
}
}

/// Implemented by types that contain a GA, simplifying the process of evolution
/// to only require a starting population and a fitness evaluator.
protocol EvolutionWrapper {
public protocol EvolutionWrapper {
associatedtype Eval: FitnessEvaluator

/// The fitness evaluator that the GA uses.
Expand All @@ -33,7 +39,7 @@ protocol EvolutionWrapper {
}

extension EvolutionWrapper {
mutating func afterEachEpoch(_ afterEachEpochFn: @escaping (Int) -> ()) {
mutating public func afterEachEpoch(_ afterEachEpochFn: @escaping (Int) -> ()) {
afterEachEpochFns.append(afterEachEpochFn)
}
}
13 changes: 7 additions & 6 deletions Sources/Evolution/FitnessEvaluator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,22 @@
import Foundation

/// Implemented by types that can evaluate fitnesses for an associated genome.
protocol FitnessEvaluator {
public protocol FitnessEvaluator {
associatedtype G: Genome
}

/// The result from an organism's fitness evaluation.
struct FitnessResult {
var fitness: Double
public struct FitnessResult {
public var fitness: Double

init(fitness: Double) {
/// Creates a new fitness result from a single fitness metric.
public init(fitness: Double) {
self.fitness = fitness
}
}

/// Implemented by types that can evaluate fitnesses synchronously.
protocol SynchronousFitnessEvaluator: FitnessEvaluator {
public protocol SynchronousFitnessEvaluator: FitnessEvaluator {
/// Returns the fitness value for a given organism. Larger fitnesses are better.
/// - Parameter organism: The organism to evaluate.
/// - Parameter solutionCallback: A function that can be called when a stopping condition is reached.
Expand All @@ -32,7 +33,7 @@ protocol SynchronousFitnessEvaluator: FitnessEvaluator {


/// Implemented by types that can evaluate fitnesses asynchronously.
protocol AsynchronousFitnessEvaluator: FitnessEvaluator {
public protocol AsynchronousFitnessEvaluator: FitnessEvaluator {
/// Submits a request for the fitness value for a given organism.
mutating func requestFitnessFor(organism: Organism<G>)
/// Checks if the evaluator has a fitness for the given organism, and
Expand Down
14 changes: 7 additions & 7 deletions Sources/Evolution/Population.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,24 @@
//

/// Defines broad types of genetic algorithms.
enum EvolutionAlgorithmType {
public enum EvolutionAlgorithmType {
/// A standard, single-objective genetic algorithm.
case standard
}

/// A world of organisms, genericized by a type of genome.
class Population<G: Genome> {
public class Population<G: Genome> {

typealias Environment = G.Environment
public typealias Environment = G.Environment

/// The environment configuration that the population is subject to.
var environment: Environment
public var environment: Environment

/// The type of evolution that this population undergoes.
let evolutionType: EvolutionAlgorithmType
public let evolutionType: EvolutionAlgorithmType

/// The organisms in the world. Fit organisms are at the end when sorted.
var organisms: [Organism<G>] = []
public var organisms: [Organism<G>] = []

/// The current generation.
private(set) public var generation = 0
Expand All @@ -39,7 +39,7 @@ class Population<G: Genome> {
private(set) internal var averageFitness: Double = 0.0

/// Creates a new, empty population with the given environment configuration.
init(environment: Environment, evolutionType: EvolutionAlgorithmType) {
public init(environment: Environment, evolutionType: EvolutionAlgorithmType) {
self.environment = environment
self.evolutionType = evolutionType
}
Expand Down
Loading

0 comments on commit 6154861

Please sign in to comment.