Visualizing the Genealogy of the Tiger Evolution
Genetic Algorithms in Elixir — by Sean Moriarity (76 / 101)
👈 Chapter 10 Visualizing the Results | TOC | Visualizing Basic Statistics 👉
In the previous chapter, you fully integrated some basic tracking mechanisms into your genetic algorithm framework. These mechanisms allowed you to track statistics on age, fitness, and pretty much any other aspect of your genetic algorithm. You also created a mechanism for tracking the genealogy of your evolution. Remember, genealogy is a family tree of the entire evolution.
In this section, you’ll learn how to export the genealogy tree and visualize it using a third-party tool.
Start by opening tiger_simulation.exs. At the moment, it looks like this:
defmodule TigerSimulation do
@behaviour Problem
alias Types.Chromosome
@impl true
def genotype do
genes = for _ <- 1..8, do: Enum.random(0..1)
%Chromosome{genes: genes, size: 8}
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(tundra_scores)
|> Enum.map(fn {t, s} -> t*s end)
|> Enum.sum()
end
@impl true
def terminate?(_population, generation), do: generation == 1000
def average_tiger(population) do
genes = Enum.map(population, & &1.genes)
fitnesses = Enum.map(population, & &1.fitness)
ages = Enum.map(population, & &1.age)
num_tigers = length(population)
avg_fitness = Enum.sum(fitnesses) / num_tigers
avg_age = Enum.sum(ages) / num_tigers
avg_genes =
genes
|> Enum.zip()
|> Enum.map(& Enum.sum(Tuple.to_list(&1)) / num_tigers)
%Chromosome{genes: avg_genes, age: avg_age, fitness: avg_fitness}
end
end
tiger = Genetic.run(TigerSimulation,
population_size: 20,
selection_rate: 0.9,
mutation_rate: 0.1,
statistics:
%{average_tiger: &TigerSimulation.average_tiger/1})
IO.write("\n")
IO.inspect(tiger)
genealogy = Utilities.Genealogy.get_tree()
IO.inspect(Graph.vertices(genealogy))You’ll need to start by adjusting the termination criteria. Currently, the algorithm is set to terminate after 1000 generations. For the purposes of this chapter, you’ll decrease this number significantly to make it easier to visualize the genealogy. In this example, you’ll decrease the number of generations to 1 and the population size to 5:
defmodule TigerSimulation do
# ...
def terminate?(_population, generation), do: generation == 0
# ...
end
tiger = Genetic.run(TigerSimulation,
population_size: 2,
selection_rate: 1.0,
mutation_rate: 0.0,
statistics:
%{average_tiger: &TigerSimulation.average_tiger/1})Next, you need to export your genealogy tree to a DOT file, like this:
genealogy = Utilities.Genealogy.get_tree()
{:ok, dot} = Graph.Serializers.DOT.serialize(genealogy)
{:ok, dotfile} = File.open("tiger_simulation.dot", [:write])
:ok = IO.binwrite(dotfile, dot)
:ok = File.close(dotfile)In this snippet, you serialize the genealogy tree to a DOT binary using the libgraph API. DOT is a standard serialized graph format. You then create a new file and write the serialized graph to it.
When you run your algorithm, you’ll see tiger_simulation.dot in your main directory. Now, to visualize, you can download Graphviz[6] or navigate to Webgraphviz[7] and copy the contents of tiger_simulation.dot into the text field. Your genealogy tree will end up looking something like the image.

Notice the genealogy is essentially a tree starting from 2 chromosomes in the first generation and spanning out from there. This type of visualization is excellent in showing how the fittest chromosome transformed over time.
👈 Chapter 10 Visualizing the Results | TOC | Visualizing Basic Statistics 👉
Genetic Algorithms in Elixir by Sean Moriarity can be purchased in other book formats directly from the Pragmatic Programmers. If you notice a code error or formatting mistake, please let us know here so that we can fix it.

