Chapter 9/10 ebook-1.02 Genealogy not working well.
As already mentioned in one of the errata the ID of every chromosome is the same.
So plotting the tree with libgraph gives many problems.
For instance parents are not unique, they are all inserted.
I did change a few things. Nodes are now unique.
Labels are much smaller, because I changed them to fitness.
You now get a nice plot with small nodes.
This is the relevant part of genealogy.ex:
def handle_cast({:add_chromosome, parent, child}, genealogy) do
new_genealogy =
genealogy
|> Graph.add_vertex(child.id, child.fitness)
|> Graph.add_edge(parent.id, child.id)
{:noreply, new_genealogy}
end
def handle_cast({:add_chromosome, parent_a, parent_b, child}, genealogy) do
new_genealogy =
genealogy
|> Graph.add_vertex(child.id, child.fitness)
|> Graph.add_edge(parent_a.id, child.id)
|> Graph.add_edge(parent_b.id, child.id)
{:noreply, new_genealogy}
end
The last part of crossover().
ID’s are now unique. Fitness are assigned
def crossover(population, problem, opts \\ []) do
................
................
c1 = %Chromosome{
c1
| id: Base.encode16(:crypto.strong_rand_bytes(64)),
fitness: problem.fitness_function(c1)
}
c2 = %Chromosome{
c2
| id: Base.encode16(:crypto.strong_rand_bytes(64)),
fitness: problem.fitness_function(c2)
}
Utilities.Genealogy.add_chromosome(p1, p2, c1)
Utilities.Genealogy.add_chromosome(p1, p2, c2)
[c1 | [c2 | acc]]
and of mutation():
def mutation(population, problem, opts \\ []) do
.......
.......
mutant = %Chromosome{
mutant
| id: Base.encode16(:crypto.strong_rand_bytes(64)),
fitness: problem.fitness_function(mutant)
}
Utilities.Genealogy.add_chromosome(c, mutant)
mutant
I did use the following tiger_simulation, and watch the change in genotype:
defmodule TigerSimulation do
@behaviour Problem
alias Types.Chromosome
@impl true
def genotype do
genes = for _ <- 1..8, do: Enum.random(0..1)
chromo = %Chromosome{genes: genes, size: 8, id: Base.encode16(:crypto.strong_rand_bytes(64))}
fitness = fitness_function(chromo)
%Chromosome{chromo | fitness: fitness}
end
@impl true
def fitness_function(chromosome) do
tropic_scores = [0.0, 3.0, 2.0, 1.0, 0.5, 1.0, -1.0, 0.0]
# tundra_scores = [1.0, 3.0, -2.0, -1.0, 0.5, 2.0, 1.0, 0.0]
traits = chromosome.genes
traits
|> Enum.zip(tropic_scores)
|> Enum.map(fn {t, s} -> t * s end)
|> Enum.sum()
end
@impl true
def terminate?(_population, generation), do: generation == 10
end
_tiger =
Genetic.run(TigerSimulation,
population_size: 8,
selection_rate: 0.8,
mutation_rate: 0.1
)
genealogy = Utilities.Genealogy.get_tree()
{:ok, dot} = Graph.Serializers.DOT.serialize(genealogy)
{:ok, dotfile} = File.open("tiger_simulation.dot", [:write]) #staat in hoofd-directory
:ok = IO.binwrite(dotfile, dot)
:ok = File.close(dotfile)
and in evolve()
........
.......
children = crossover(parents, problem, opts)
......
mutants = mutation(population, problem, opts)
Tip: In Intellij Idea you have a nice plugin for .dot files: Dot plus!
If selection <1 you wil see also loose nodes. Nice!
And some other suggestions:
def run(problem, opts \\ []) do
population = initialize(&problem.genotype/0, opts)
start_bestfitness = %Chromosome{hd(population) | fitness: 0.0}
population
|> evolve(problem, 0, start_bestfitness, bestgeneration = 0, opts)
end
def evolve(population, problem, generation, bestfitness, bestgeneration, opts \\ []) do
population = evaluate(population, &problem.fitness_function/1, opts)
best = hd(population)
best1 = best.fitness
best2 = bestfitness.fitness
bestfitness =
if best1 > best2 do
best
else
bestfitness
end
bestgeneration =
if best1 > best2 do
generation
else
bestgeneration
end
IO.inspect(best, label: "\n\ncurrent best:")
IO.write("Generation: #{generation}")
statistics(population, generation, opts)
if problem.terminate?(population, generation) do
............................
.............................