Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Genetic algorithms in Elixir - Chapter 2

notebooks/chapter2.livemd

Genetic algorithms in Elixir - Chapter 2

Overview

In this chapter we develop a generic framework that can be reused across multiple different problem domains. This is accomplished by essentially rewriting the algorithm from chapter 1, but with certain key parts made configurable or externalized through the use of first class functions.

The framework

The core functions required for evolution are as follows:

defmodule LiveGenetic do
  def initialize(genotype, opts \\ []) do
    population_size = Keyword.get(opts, :population_size, 100)
    for _ <- 1..population_size, do: genotype.()
  end

  def evaluate(population, fitness_function, opts \\ []) do
    population
    |> Enum.sort_by(fitness_function, &amp;>=/2)
  end

  def select(population, opts \\ []) do
    population
    |> Enum.chunk_every(2)
    |> Enum.map(&amp;List.to_tuple(&amp;1))
  end

  def crossover(population, opts \\ []) do
    population
    |> Enum.reduce([], fn {p1, p2}, acc ->
      cx_point = :rand.uniform(length(p1))
      {{h1, t1}, {h2, t2}} = {Enum.split(p1, cx_point), Enum.split(p2, cx_point)}
      {c1, c2} = {h1 ++ t2, h2 ++ t1}
      [c1, c2 | acc]
    end)
  end

  def mutation(population, opts \\ []) do
    population
    |> Enum.map(fn chromosome ->
      if :rand.uniform() < 0.05 do
        Enum.shuffle(chromosome)
      else
        chromosome
      end
    end)
  end

  def run(fitness_function, genotype, max_fitness, opts \\ []) do
    population = initialize(genotype)

    population
    |> evolve(fitness_function, max_fitness, opts)
  end

  def evolve(population, fitness_function, max_fitness, opts \\ []) do
    population = evaluate(population, fitness_function, opts)
    best = hd(population)
    IO.write("\current Best: #{fitness_function.(best)}")

    if fitness_function.(best) == max_fitness do
      best
    else
      population
      |> select(opts)
      |> crossover(opts)
      |> mutation(opts)
      |> evolve(fitness_function, max_fitness, opts)
    end
  end
end

These functions are combined in a cohesive unit through the following two functions:

genotype = fn -> for _ <- 1..1000, do: Enum.random(0..1) end

fitness_function = fn chromosome -> Enum.sum(chromosome) end
max_fitness = 1000

soln = LiveGenetic.run(fitness_function, genotype, max_fitness)