A multi-representation genetic algorithm library.
Petri supports three chromosome encodings:
Petri.Chromosome.Real— continuous values with per-gene boundsPetri.Chromosome.Permutation— integer permutations (e.g. TSP tours)Petri.Chromosome.Binary— bit strings for subset selection
Each encoding has its own crossover and mutation operators. Selection, termination, and the generational engine work across all encodings.
Quick start
iex> fitness = fn c -> Petri.Chromosome.genes(c) |> Enum.sum() end
iex> result = Petri.run(fitness, %{
...> encoding: :binary, length: 10,
...> population_size: 20, max_generations: 50, seed: 42
...> })
iex> %Petri.Result{} = result
iex> {_chrom, f} = result.best
iex> f
10Running the examples
Three standalone .exs scripts in the examples/ directory demonstrate
each encoding on a realistic problem:
elixir examples/tsp.exs # Berlin52 TSP (permutation)
elixir examples/ml_hyperparams.exs # Hyperparameter tuning (real)
elixir examples/feature_selection.exs # Feature subset selection (binary)
Summary
Functions
Validates a config without running the GA.
Returns {:ok, config} or {:error, reasons}.
Example
iex> {:ok, config} = Petri.configure(%{
...> encoding: :binary, length: 8,
...> population_size: 20, max_generations: 10
...> })
iex> config.encoding
:binary
iex> {:error, err} = Petri.configure(%{
...> encoding: :binary, crossover: :blx_alpha,
...> length: 8, population_size: 20, max_generations: 10
...> })
iex> is_list(err)
true
Runs a genetic algorithm.
fitness_fn is a function (chromosome -> fitness) where higher fitness
is better. The GA maximizes.
config is a map. Required fields depend on the encoding.
See Petri.Config.parse/1 for the full schema.
Config (binary encoding)
iex> fitness = fn c -> Petri.Chromosome.genes(c) |> Enum.sum() end
iex> result = Petri.run(fitness, %{
...> encoding: :binary, length: 8,
...> population_size: 30, max_generations: 100, seed: 99
...> })
iex> {_chrom, f} = result.best
iex> f
8Config (real encoding)
iex> fitness = fn %Petri.Chromosome.Real{genes: [x, y]} -> -(x*x + y*y) end
iex> result = Petri.run(fitness, %{
...> encoding: :real, bounds: [{-5.0, 5.0}, {-5.0, 5.0}],
...> selection: :tournament,
...> population_size: 50, max_generations: 50, seed: 1
...> })
iex> {_chrom, r2} = result.best
iex> r2 < 0.0
trueConfig (permutation encoding)
iex> fitness = fn %Petri.Chromosome.Permutation{genes: g} ->
iex> # Count adjacent pairs in ascending order
...> g |> Enum.chunk_every(2, 1, :discard) |> Enum.count(fn [a, b] -> a < b end)
...> end
iex> result = Petri.run(fitness, %{
...> encoding: :permutation, n: 20,
...> selection: :tournament,
...> population_size: 50, max_generations: 100, seed: 1,
...> crossover: :ox, mutation: :swap
...> })
iex> {_chrom, p_fit} = result.best
iex> p_fit > 10
true