diff --git a/lib/genetic.ex b/lib/genetic.ex index ef07b9f..d6617ad 100644 --- a/lib/genetic.ex +++ b/lib/genetic.ex @@ -6,7 +6,7 @@ defmodule Genetic do for _ <- 1..population_size, do: genotype.() end - def evaluate(population, fitness_function, opts \\ []) do + def evaluate(population, fitness_function, _opts \\ []) do population |> Enum.map( fn chromosome -> @@ -18,13 +18,13 @@ defmodule Genetic do |> Enum.sort_by(& &1.fitness, &>=/2) end - def select(population, opts \\ []) do + def select(population, _opts \\ []) do population |> Enum.chunk_every(2) |> Enum.map(&List.to_tuple(&1)) end - def crossover(population, opts \\ []) do + def crossover(population, _opts \\ []) do population |> Enum.reduce([], fn {p1, p2}, acc -> @@ -38,12 +38,12 @@ defmodule Genetic do ) end - def mutation(population, opts \\ []) do + def mutation(population, _opts \\ []) do population |> Enum.map( fn chromosome -> if :rand.uniform() < 0.05 do - %Chromosome{chromosome | genes: Enum.shuffle(chromosome)} + %Chromosome{chromosome | genes: Enum.shuffle(chromosome.genes)} else chromosome end @@ -53,22 +53,24 @@ defmodule Genetic do def run(problem, opts \\ []) do population = initialize(&problem.genotype/0) + first_generation = 0 + population - |> evolve(problem, opts) + |> evolve(problem, first_generation, opts) end - def evolve(population, problem, opts \\ []) do + def evolve(population, problem, generation, opts \\ []) do population = evaluate(population, &problem.fitness_function/1, opts) best = hd(population) IO.write("\rCurrent Best: #{best.fitness}") - if problem.terminate?(population) do + if problem.terminate?(population, generation) do best else population |> select(opts) |> crossover(opts) |> mutation(opts) - |> evolve(problem, opts) + |> evolve(problem, generation+1, opts) end diff --git a/problem.ex b/lib/problem.ex similarity index 72% rename from problem.ex rename to lib/problem.ex index 0d3f56d..fcb6638 100644 --- a/problem.ex +++ b/lib/problem.ex @@ -5,5 +5,5 @@ defmodule Problem do @callback fitness_function(Chromosome.t) :: number() - @callback terminate?(Enum.t) :: boolean() + @callback terminate?(Enum.t, integer()) :: boolean() end diff --git a/scripts/cargo.exs b/scripts/cargo.exs new file mode 100644 index 0000000..8c82a3a --- /dev/null +++ b/scripts/cargo.exs @@ -0,0 +1,50 @@ +defmodule Cargo do + @behaviour Problem + alias Types.Chromosome + + @impl true + def genotype do + genes = for _ <- 1..10, do: Enum.random(0..1) + %Chromosome{genes: genes, size: 10} + end + + @impl true + def fitness_function(chromosome) do + profits = [6, 5, 8, 9, 6, 7, 3, 1, 2, 6] + weights = [10, 6, 8, 7, 10, 9, 7, 11, 6, 8] + weight_limit = 40 + + potential_profits = + chromosome.genes + |> Enum.zip(profits) + |> Enum.map(fn {g, p} -> g * p end) + |> Enum.sum() + + over_limit? = + chromosome.genes + |> Enum.zip(weights) + |> Enum.map(fn {c, w} -> c * w end) + |> Enum.sum() + |> Kernel.>(weight_limit) + + profits = if over_limit?, do: 0, else: potential_profits + profits + end + + @impl true + def terminate?(population, _generation) do + Enum.max_by(population, &Cargo.fitness_function/1).fitness == 53 + end +end + +soln = Genetic.run(Cargo, population_size: 50) + +IO.write("\n") +IO.inspect(soln) + +weight = soln.genes + |> Enum.zip([10, 6, 8, 7, 10, 9, 7, 11, 6, 8]) + |> Enum.map(fn {g, w} -> w*g end) + |> Enum.sum() + +IO.write("\nWeight is: #{weight}\n") diff --git a/scripts/one_max.exs b/scripts/one_max.exs index 5bd36bb..d56fb26 100644 --- a/scripts/one_max.exs +++ b/scripts/one_max.exs @@ -1,23 +1,24 @@ -genotype = fn -> for _ <- 1..1000, do: Enum.random(0..1) end - -fitness_function = fn chromosome -> Enum.sum(chromosome) end -max_fitness = 1000 - defmodule OneMax do + alias Types.Chromosome + @behaviour Problem - @impl Problem + @impl true def genotype() do - for _ <- 1..1000, do: Enum.random(0.. + genes = for _ <- 1..1000, do: Enum.random(0..1) + %Chromosome{genes: genes} end - @impl Problem + @impl true def fitness_function(chromosome) do Enum.sum(chromosome.genes) end + + @impl true + def terminate?(_population, generation), do: generation == 100 end -soln = Genetic.run(fitness_function, genotype, max_fitness) +soln = Genetic.run(OneMax) IO.write("\n") -IO.inspect(soln) +IO.inspect(soln.genes) diff --git a/scripts/portfolio.exs b/scripts/portfolio.exs new file mode 100644 index 0000000..f757336 --- /dev/null +++ b/scripts/portfolio.exs @@ -0,0 +1,31 @@ +defmodule Portfolio do + @behaviour Problem + alias Types.Chromosome + + @target_fitness 180 + + @impl true + def genotype do + genes = + for _ <- 1..10, do: + {:rand.uniform(10), :rand.uniform(10)} + + %Chromosome{genes: genes, size: 10} + end + + @impl true + def fitness_function(chromosome) do + chromosome.genes + |> Enum.map(fn {roi, risk} -> 2 * roi - risk end) + |> Enum.sum() + end + + @impl true + def terminate?(population, _generation) do + max_value = Enum.max_by(population, &Portfolio.fitness_function/1) + max_value > @target_fitness + end +end + + +soln = Genetic.run(Portfolio) diff --git a/scripts/speller.exs b/scripts/speller.exs new file mode 100644 index 0000000..03f2455 --- /dev/null +++ b/scripts/speller.exs @@ -0,0 +1,25 @@ +defmodule Speller do + @behaviour Problem + alias Types.Chromosome + + def genotype do + genes = + Stream.repeatedly(fn -> Enum.random(?a..?z) end) + |> Enum.take(34) + %Chromosome{genes: genes, size: 34} + end + + def fitness_function(chromosome) do + target = "supercalifragilisticexpialidocious" + guess = List.to_string(chromosome.genes) + String.jaro_distance(target, guess) + end + + def terminate?([best | _]), do: best.fitness == 1 + +end + +soln = Genetic.run(Speller) + +IO.write("\n") +IO.inspect(soln)