genetic

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

commit a6df082f4d832e98fb633590939adaf90f4726ec
parent 1f36b04ae4e0a9265ec327b1954af2adc0704b69
Author: dracuxan <[email protected]>
Date:   Fri, 24 Apr 2026 11:26:00 +0530

upd: optimizations using paralellizations and nifs

Diffstat:
M.gitignore | 2++
Mlib/genetic.ex | 23++++++++++++++++++-----
Mlib/toolbox/crossover.ex | 2+-
Mmix.exs | 27+++++++++++++++++++++++++++
Mmix.lock | 5+++++
Mscripts/cargo.exs | 2+-
Mscripts/n_queens.exs | 49++++++++++++++++++++++++++++++++++---------------
Asrc/genetic.c | 17+++++++++++++++++
8 files changed, 105 insertions(+), 22 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -27,3 +27,5 @@ genetic-*.tar games/ *.dot + +priv/ diff --git a/lib/genetic.ex b/lib/genetic.ex @@ -1,5 +1,12 @@ defmodule Genetic do alias Types.Chromosome + @on_load :load_nif + def load_nif do + path = ~c"./priv/genetic" + :erlang.load_nif(path, 0) + end + + def xor96, do: 0 def run(problem, opts \\ []) do population = initialize(&problem.genotype/0, opts) @@ -10,14 +17,14 @@ defmodule Genetic do def initialize(genotype, opts \\ []) do population_size = Keyword.get(opts, :population_size, 100) - population = Enum.map(1..population_size, fn _ -> genotype.() end) + population = pmap(1..population_size, fn _ -> genotype.() end) Utilities.Genealogy.add_chromosomes(population) population end def evaluate(population, fitness_function, _opts \\ []) do population - |> Enum.map(fn chromosome -> + |> pmap(fn chromosome -> fitness = fitness_function.(chromosome) age = 1 %Chromosome{genes: chromosome.genes, fitness: fitness, age: age} @@ -45,13 +52,13 @@ defmodule Genetic do parents = parents |> Enum.chunk_every(2) - |> Enum.map(&List.to_tuple(&1)) + |> pmap(&List.to_tuple(&1)) {parents, MapSet.to_list(leftover)} end def crossover(population, opts \\ []) do - crossover_fn = Keyword.get(opts, :crossover_type, &Toolbox.Crossover.order_one/3) + crossover_fn = Keyword.get(opts, :crossover_type, &Toolbox.Crossover.single_point/3) population |> Enum.reduce( @@ -72,7 +79,7 @@ defmodule Genetic do population |> Enum.take_random(n) - |> Enum.map(&apply(mutation_fn, [&1, opts])) + |> pmap(&apply(mutation_fn, [&1, opts])) end def evolve(population, problem, generation, opts \\ []) do @@ -100,6 +107,12 @@ defmodule Genetic do apply(strategy, [parents, offsprings, leftover, opts]) end + def pmap(collection, func) do + collection + |> Enum.map(&Task.async(fn -> func.(&1) end)) + |> Enum.map(&Task.await(&1)) + end + def statistics(population, generation, opts \\ []) do default_stats = [ min_fitness: &Enum.min_by(&1, fn c -> c.fitness end).fitness, diff --git a/lib/toolbox/crossover.ex b/lib/toolbox/crossover.ex @@ -31,7 +31,7 @@ defmodule Toolbox.Crossover do end def single_point(p1, p2, _opts \\ []) do - cx_point = :rand.uniform(length(p1.genes) - 1) + cx_point = rem(Genetic.xor96(), length(p1.genes)) {{head1, tail1}, {head2, tail2}} = {Enum.split(p1.genes, cx_point), Enum.split(p2.genes, cx_point)} diff --git a/mix.exs b/mix.exs @@ -1,3 +1,27 @@ +defmodule Mix.Tasks.Compile.Cnif do + use Mix.Task.Compiler + + def run(_args) do + File.mkdir_p!("priv") + + {output, code} = + System.cmd( + "gcc", + [ + "-fpic", + "-shared", + "-o", + "priv/genetic.so", + "src/genetic.c" + ], + stderr_to_stdout: true + ) + + IO.puts(output) + if code == 0, do: :ok, else: {:error, []} + end +end + defmodule Genetic.MixProject do use Mix.Project @@ -7,6 +31,7 @@ defmodule Genetic.MixProject do version: "0.1.0", elixir: "~> 1.18", start_permanent: Mix.env() == :prod, + compilers: [:cnif] ++ Mix.compilers(), deps: deps() ] end @@ -24,6 +49,8 @@ defmodule Genetic.MixProject do [ {:libgraph, "~> 0.16.0"}, {:gnuplot, "~> 1.22"}, + {:benchee, "~> 1.5.0"}, + {:exprof, "~> 0.2.4"}, {:alex, "~> 0.3.2"} ] end diff --git a/mix.lock b/mix.lock @@ -1,11 +1,16 @@ %{ "alex": {:hex, :alex, "0.3.2", "b6fa1d744783707e1ff068ecdef2eb993f9b05a2ebe9c97ba67b05902c0aaa9c", [:mix], [], "hexpm", "9d40610d06857f5a321923f28c44ca08575ad64594cbbfa4352eb115e5c0566e"}, + "benchee": {:hex, :benchee, "1.5.0", "4d812c31d54b0ec0167e91278e7de3f596324a78a096fd3d0bea68bb0c513b10", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.1", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "5b075393aea81b8ae74eadd1c28b1d87e8a63696c649d8293db7c4df3eb67535"}, + "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "ex_doc": {:hex, :ex_doc, "0.40.1", "67542e4b6dde74811cfd580e2c0149b78010fd13001fda7cfeb2b2c2ffb1344d", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "bcef0e2d360d93ac19f01a85d58f91752d930c0a30e2681145feea6bd3516e00"}, + "exprintf": {:hex, :exprintf, "0.2.1", "b7e895dfb00520cfb7fc1671303b63b37dc3897c59be7cbf1ae62f766a8a0314", [:mix], [], "hexpm", "20a0e8c880be90e56a77fcc82533c5d60c643915c7ce0cc8aa1e06ed6001da28"}, + "exprof": {:hex, :exprof, "0.2.4", "13ddc0575a6d24b52e7c6809d2a46e9ad63a4dd179628698cdbb6c1f6e497c98", [:mix], [{:exprintf, "~> 0.2", [hex: :exprintf, repo: "hexpm", optional: false]}], "hexpm", "0884bcb66afc421c75d749156acbb99034cc7db6d3b116c32e36f32551106957"}, "gnuplot": {:hex, :gnuplot, "1.22.270", "541da1d4be2acbcb2a53105a7c2f31d91127811858b522c1a2290ed5844b9624", [:mix], [{:ex_doc, "~> 0.28", [hex: :ex_doc, repo: "hexpm", optional: false]}], "hexpm", "2870982804ac79a93eebf31c07a507a1825bc23c782907da48d653cf970a4d41"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.3", "4252d5d4098da7415c390e847c814bad3764c94a814a0b4245176215615e1035", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "953297c02582a33411ac6208f2c6e55f0e870df7f80da724ed613f10e6706afd"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "statistex": {:hex, :statistex, "1.1.0", "7fec1eb2f580a0d2c1a05ed27396a084ab064a40cfc84246dbfb0c72a5c761e5", [:mix], [], "hexpm", "f5950ea26ad43246ba2cce54324ac394a4e7408fdcf98b8e230f503a0cba9cf5"}, } diff --git a/scripts/cargo.exs b/scripts/cargo.exs @@ -37,7 +37,7 @@ defmodule Cargo do end end -{soln, _} = Genetic.run(Cargo, crossover_type: &Toolbox.Crossover.single_point/2) +{soln, _} = Genetic.run(Cargo, crossover_type: &Toolbox.Crossover.single_point/3) IO.puts("\nsolution: #{inspect(soln)}") weight = diff --git a/scripts/n_queens.exs b/scripts/n_queens.exs @@ -2,7 +2,7 @@ defmodule NQueens do alias Types.Chromosome @behaviour Problem - @max_queens 14 + @max_queens 16 @target_fitness @max_queens + 1 @size @target_fitness @@ -33,6 +33,24 @@ defmodule NQueens do best = hd(population) best.fitness == @target_fitness end + + def map_queens(soln) do + size = length(soln) + + for i <- 1..size do + row_string = + for j <- 1..size do + if(Enum.at(soln, i) == j) do + " Q " + else + " . " + end + end + |> Enum.join() + + IO.puts(row_string) + end + end end {soln, generation} = @@ -43,18 +61,19 @@ end crossover_type: &Toolbox.Crossover.order_one/3 ) -IO.puts("\nfinal answer: #{inspect(soln)}\ngenrations passed: #{generation}") - -stats = - :ets.tab2list(:statistics) - |> Enum.sort_by(fn {gen, _stat} -> gen end) - |> Enum.map(fn {gen, stats} -> [gen, stats.mean_fitness] end) +IO.puts("\nfinal answer: #{inspect(soln.genes)}\ngenrations passed: #{generation}") +NQueens.map_queens(soln.genes) -{:ok, _} = - Gnuplot.plot( - [ - [:set, :title, "mean fitness versus generation (n__queens)"], - [:plot, "-", :with, :points] - ], - [stats] - ) +# stats = +# :ets.tab2list(:statistics) +# |> Enum.sort_by(fn {gen, _stat} -> gen end) +# |> Enum.map(fn {gen, stats} -> [gen, stats.mean_fitness] end) +# +# {:ok, _} = +# Gnuplot.plot( +# [ +# [:set, :title, "mean fitness versus generation (n__queens)"], +# [:plot, "-", :with, :points] +# ], +# [stats] +# ) diff --git a/src/genetic.c b/src/genetic.c @@ -0,0 +1,17 @@ +#include <erl_nif.h> +#include <inttypes.h> +#include <stdint.h> + +static uint32_t x = 123456789, y = 362436069, z = 52128629; + +static ERL_NIF_TERM xor96(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { + uint32_t t = (x ^ (x << 10)); + x = y; + y = z; + z = (z ^ (z >> 26)) ^ (t ^ (t >> 5)); + + return enif_make_int(env, z); +} + +static ErlNifFunc nif_funcs[] = {{"xor96", 0, xor96}}; +ERL_NIF_INIT(Elixir.Genetic, nif_funcs, NULL, NULL, NULL, NULL);