genetic

genetic algorithm framework
git clone igris.git:dracuxan/genetic.git
Log | Files | Refs | README

commit be4243ee492e036b70741608e0d54c4b176e5a5e
parent b7a9695c63d365787c5ec3113ed64aef1378cc6a
Author: dracuxan <[email protected]>
Date:   Thu,  9 Apr 2026 17:41:54 +0530

feat: statistics

Diffstat:
Agenetic/lib/application.ex | 12++++++++++++
Mgenetic/lib/genetic.ex | 24+++++++++++++++++++++++-
Agenetic/lib/utilities/statistics.ex | 20++++++++++++++++++++
Mgenetic/mix.exs | 4+++-
Agenetic/mix.lock | 3+++
Mgenetic/scripts/one_max.exs | 3+++
Mgenetic/scripts/speller.exs | 1+
Agenetic/scripts/tiger_simulation.exs | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 148 insertions(+), 2 deletions(-)

diff --git a/genetic/lib/application.ex b/genetic/lib/application.ex @@ -0,0 +1,12 @@ +defmodule Genetic.Application do + use Application + + def start(_type, _args) do + children = [ + {Utilities.Statistics, []} + ] + + opts = [strategy: :one_for_one, name: Genetic.Supervisor] + Supervisor.start_link(children, opts) + end +end diff --git a/genetic/lib/genetic.ex b/genetic/lib/genetic.ex @@ -17,7 +17,7 @@ defmodule Genetic do population |> Enum.map(fn chromosome -> fitness = fitness_function.(chromosome) - age = chromosome.age + 1 + age = 1 %Chromosome{chromosome | fitness: fitness, age: age} end) |> Enum.sort_by(& &1.fitness, :desc) @@ -82,6 +82,7 @@ defmodule Genetic do def evolve(population, problem, generation, opts \\ []) do population = evaluate(population, &problem.fitness_function/1, opts) + statistics(population, generation, opts) best = hd(population) fit_str = best.fitness |> :erlang.float_to_binary(decimals: 4) IO.write("\rcurrent best: #{fit_str}\tgeneration: #{generation}") @@ -97,4 +98,25 @@ defmodule Genetic do |> evolve(problem, generation + 1, opts) end end + + def statistics(population, generation, opts \\ []) do + default_stats = [ + min_fitness: &Enum.min_by(&1, fn c -> c.fitness end).fitness, + max_fitness: &Enum.max_by(&1, fn c -> c.fitness end).fitness, + mean_fitness: &(Enum.sum(Enum.map(&1, fn c -> c.fitness end)) / length(population)) + ] + + stats = Keyword.get(opts, :statistics, default_stats) + + stats_map = + stats + |> Enum.reduce( + %{}, + fn {key, func}, acc -> + Map.put(acc, key, func.(population)) + end + ) + + Utilities.Statistics.insert(generation, stats_map) + end end diff --git a/genetic/lib/utilities/statistics.ex b/genetic/lib/utilities/statistics.ex @@ -0,0 +1,20 @@ +defmodule Utilities.Statistics do + use GenServer + + def init(opts) do + :ets.new(:statistics, [:set, :public, :named_table]) + {:ok, opts} + end + + def insert(generation, statistics) do + :ets.insert(:statistics, {generation, statistics}) + end + + def lookup(generation) do + hd(:ets.lookup(:statistics, generation)) + end + + def start_link(opts) do + GenServer.start_link(__MODULE__, opts, name: __MODULE__) + end +end diff --git a/genetic/mix.exs b/genetic/mix.exs @@ -14,13 +14,15 @@ defmodule Genetic.MixProject do # Run "mix help compile.app" to learn about applications. def application do [ - extra_applications: [:logger] + extra_applications: [:logger], + mod: {Genetic.Application, []} ] end # Run "mix help deps" to learn about dependencies. defp deps do [ + {:libgraph, "~> 0.16.0"} # {:dep_from_hexpm, "~> 0.3.0"}, # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} ] diff --git a/genetic/mix.lock b/genetic/mix.lock @@ -0,0 +1,3 @@ +%{ + "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, +} diff --git a/genetic/scripts/one_max.exs b/genetic/scripts/one_max.exs @@ -44,3 +44,6 @@ end IO.puts("\nfinal answer: #{inspect(soln)}") IO.puts("generations passed: #{generation}") + +{_, tgs} = Utilities.Statistics.lookup(generation) +IO.inspect(tgs) diff --git a/genetic/scripts/speller.exs b/genetic/scripts/speller.exs @@ -28,6 +28,7 @@ end {soln, _} = Genetic.run(Speller, selection_type: &Toolbox.Selection.elite/2, + mutation_type: &Toolbox.Mutation.flip/2, crossover_type: &Toolbox.Crossover.uniform/3 ) diff --git a/genetic/scripts/tiger_simulation.exs b/genetic/scripts/tiger_simulation.exs @@ -0,0 +1,83 @@ +defmodule TigerSimulation do + alias Types.Chromosome + @behaviour Problem + @traits 8 + + # representing tiger genotype: + # + # | traits | 0 | 1 | + # +------------------+------------+------------+ + # | size | smaller | larger | + # | swimming ability | low | high | + # | fur color | light | dark | + # | fat stores | less | more | + # | activity period | diurnal | nocturnal | + # | hunting range | smaller | larger | + # | fur thickness | less thick | more thick | + # | tail length | smaller | larger | + + @impl true + def genotype do + genes = Enum.map(1..@traits, fn _ -> Enum.random(0..1) end) + %Chromosome{genes: genes, size: @traits} + end + + # scores for each trait: + # + # | traits | tropical | tundra | + # +------------------+----------+--------+ + # | size | 0.0 | 1.0 | + # | swimming ability | 3.0 | 3.0 | + # | fur color | 2.0 | -2.0 | + # | fat stores | -1.0 | 1.0 | + # | activity period | 0.5 | 0.5 | + # | hunting range | 1.0 | 2.0 | + # | fur thickness | -1.0 | 1.0 | + # | tail length | 0.0 | 0.0 | + + @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 == 50 + + def average_tiger(population) do + genes = Enum.map(population, & &1.genes) + fitness = Enum.map(population, & &1.fitness) + ages = Enum.map(population, & &1.age) + num_tigers = length(population) + + avg_fitness = Enum.sum(fitness) / num_tigers + avg_age = Enum.sum(ages) / num_tigers + + avg_genes = + genes + |> Enum.zip() + |> Enum.map(fn t -> Enum.sum(Tuple.to_list(t)) / num_tigers end) + + %Chromosome{genes: avg_genes, age: avg_age, fitness: avg_fitness, size: length(avg_genes)} + end +end + +{_tiger, generation} = + Genetic.run(TigerSimulation, + population_size: 100, + selection_rate: 0.9, + mutation_rate: 0.1, + mutation_type: &Toolbox.Mutation.flip/2, + crossover_type: &Toolbox.Crossover.single_point/3, + statistics: %{average_tiger: &TigerSimulation.average_tiger/1} + ) + +{_, lts} = Utilities.Statistics.lookup(generation) + +IO.puts("\n #{inspect(lts)}")