genetic

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

commit 7bb5ee8d7422377810512633f15fb4b8ecfe1439
parent 83b2110dd6c877b1d7569e9d3052f0d9ee059ce7
Author: dracuxan <[email protected]>
Date:   Wed,  1 Apr 2026 16:01:15 +0530

upd

Diffstat:
Mgenetic/.formatter.exs | 2+-
Mgenetic/lib/genetic.ex | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agenetic/lib/problem.ex | 7+++++++
Agenetic/lib/types/chromosome.ex | 11+++++++++++
Agenetic/scripts/cargo.exs | 36++++++++++++++++++++++++++++++++++++
Agenetic/scripts/one_max.exs | 23+++++++++++++++++++++++
Agenetic/scripts/speller.exs | 29+++++++++++++++++++++++++++++
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)}")