commit be4243ee492e036b70741608e0d54c4b176e5a5e
parent b7a9695c63d365787c5ec3113ed64aef1378cc6a
Author: dracuxan <[email protected]>
Date: Thu, 9 Apr 2026 17:41:54 +0530
feat: statistics
Diffstat:
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)}")