commit 7bb5ee8d7422377810512633f15fb4b8ecfe1439
parent 83b2110dd6c877b1d7569e9d3052f0d9ee059ce7
Author: dracuxan <[email protected]>
Date: Wed, 1 Apr 2026 16:01:15 +0530
upd
Diffstat:
7 files changed, 182 insertions(+), 1 deletion(-)
diff --git a/genetic/.formatter.exs b/genetic/.formatter.exs
@@ -1,4 +1,4 @@
# Used by "mix format"
[
- inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
+ inputs: ["{mix,.formatter}.exs", "{config,lib,test,scripts,types}/**/*.{ex,exs}"]
]
diff --git a/genetic/lib/genetic.ex b/genetic/lib/genetic.ex
@@ -0,0 +1,75 @@
+defmodule Genetic do
+ alias Types.Chromosome
+
+ def run(problem, opts \\ []) do
+ population = initialize(&problem.genotype/0)
+
+ population
+ |> evolve(problem, opts)
+ end
+
+ def initialize(genotype, opts \\ []) do
+ population_size = Keyword.get(opts, :population_size, 100)
+ Enum.map(1..population_size, fn _ -> genotype.() end)
+ end
+
+ def evaluate(population, fitness_function, _opts \\ []) do
+ population
+ |> Enum.map(fn chromosome ->
+ fitness = fitness_function.(chromosome)
+ age = chromosome.age + 1
+ %Chromosome{chromosome | fitness: fitness, age: age}
+ end)
+ |> Enum.sort_by(& &1.fitness, :desc)
+ end
+
+ def select(population, _opts \\ []) do
+ population
+ |> Enum.chunk_every(2)
+ |> Enum.map(&List.to_tuple/1)
+ end
+
+ def crossover(population, _opts \\ []) do
+ population
+ |> Enum.reduce(
+ [],
+ fn {p1, p2}, acc ->
+ cx_point = :rand.uniform(length(p1.genes))
+
+ {{h1, t1}, {h2, t2}} =
+ {Enum.split(p1.genes, cx_point), Enum.split(p2.genes, cx_point)}
+
+ {c1, c2} = {%Chromosome{p1 | genes: h1 ++ t2}, %Chromosome{p2 | genes: h2 ++ t1}}
+
+ [c1, c2 | acc]
+ end
+ )
+ end
+
+ def mutation(population, _opts \\ []) do
+ population
+ |> Enum.map(fn chromosome ->
+ if :rand.uniform() < 0.05 do
+ %Chromosome{chromosome | genes: Enum.shuffle(chromosome.genes)}
+ else
+ chromosome
+ end
+ end)
+ end
+
+ def evolve(population, problem, opts \\ []) do
+ population = evaluate(population, &problem.fitness_function/1, opts)
+ best = hd(population)
+ IO.write("\rcurrent best: #{best.fitness}")
+
+ if problem.terminate?(population) do
+ best
+ else
+ population
+ |> select(opts)
+ |> crossover(opts)
+ |> mutation(opts)
+ |> evolve(problem, opts)
+ end
+ end
+end
diff --git a/genetic/lib/problem.ex b/genetic/lib/problem.ex
@@ -0,0 +1,7 @@
+defmodule Problem do
+ alias Types.Chromosome
+
+ @callback genotype :: Chromosome.t()
+ @callback fitness_function(Chromosome.t()) :: number()
+ @callback terminate?(Enum.t()) :: boolean()
+end
diff --git a/genetic/lib/types/chromosome.ex b/genetic/lib/types/chromosome.ex
@@ -0,0 +1,11 @@
+defmodule Types.Chromosome do
+ @type t :: %__MODULE__{
+ genes: Enum.t(),
+ size: integer,
+ fitness: number,
+ age: integer
+ }
+
+ @enforce_keys :genes
+ defstruct [:genes, size: 0, fitness: 0, age: 0]
+end
diff --git a/genetic/scripts/cargo.exs b/genetic/scripts/cargo.exs
@@ -0,0 +1,36 @@
+defmodule Cargo do
+ alias Types.Chromosome
+ @behaviour Problem
+
+ @impl true
+ def genotype do
+ genes = Enum.map(1..10, fn _ -> Enum.random(0..1) end)
+ %Chromosome{genes: genes, size: 10}
+ end
+
+ @impl true
+ def fitness_function(chromosome) do
+ profits = [6, 5, 8, 9, 6, 7, 3, 1, 2, 6]
+
+ profits
+ |> Enum.zip(chromosome.genes)
+ |> Enum.map(fn {p, g} -> p * g end)
+ |> Enum.sum()
+ end
+
+ @impl true
+ def terminate?(population) do
+ Enum.max_by(population, &Cargo.fitness_function/1).fitness == 53
+ end
+end
+
+soln = Genetic.run(Cargo)
+IO.puts("\nsolution: #{inspect(soln)}")
+
+weight =
+ soln.genes
+ |> Enum.zip([10, 6, 8, 7, 10, 9, 7, 11, 6, 8])
+ |> Enum.map(fn {g, w} -> g * w end)
+ |> Enum.sum()
+
+IO.puts("weight: #{weight}")
diff --git a/genetic/scripts/one_max.exs b/genetic/scripts/one_max.exs
@@ -0,0 +1,23 @@
+defmodule OneMax do
+ alias Types.Chromosome
+ @behaviour Problem
+
+ @impl true
+ def genotype do
+ genes = Enum.map(1..1000, fn _ -> Enum.random(0..1) end)
+ %Chromosome{genes: genes, size: 1000}
+ end
+
+ @impl true
+ def fitness_function(chromosome) do
+ Enum.sum(chromosome.genes)
+ end
+
+ @impl true
+ def terminate?([best | _]) do
+ best.fitness == 1000
+ end
+end
+
+soln = Genetic.run(OneMax)
+IO.puts("\nfinal answer: #{inspect(soln.genes)}")
diff --git a/genetic/scripts/speller.exs b/genetic/scripts/speller.exs
@@ -0,0 +1,29 @@
+defmodule Speller do
+ alias Types.Chromosome
+ @behaviour Problem
+
+ @impl true
+ def genotype do
+ genes =
+ Stream.repeatedly(fn -> Enum.random(?a..?z) end)
+ |> Enum.take(8)
+
+ %Chromosome{genes: genes, size: 8}
+ end
+
+ @impl true
+ def fitness_function(chromosome) do
+ target = "dracuxan"
+ guess = List.to_string(chromosome.genes)
+ String.jaro_distance(target, guess)
+ end
+
+ @impl true
+ def terminate?(population) do
+ [best | _] = population
+ best.fitness == 1
+ end
+end
+
+soln = Genetic.run(Speller)
+IO.puts("\nfinal answer: #{inspect(soln)}")