defmodule Jido.Evolve.Examples.HelloWorld do
@moduledoc """
A simple example of evolving text towards a target string.
This example demonstrates how to use Jido.Evolve to evolve a string
towards the target "Hello, world!" using random mutations and
tournament selection with adaptive mutation rates.
"""
use Jido.Evolve.Fitness
@target "Hello, world!"
@doc """
Fitness function that measures similarity to the target string.
Higher scores indicate better fitness (closer to target).
"""
def evaluate(text, _context) do
# Count matching characters at correct positions
target_chars = String.graphemes(@target)
text_chars = String.graphemes(text)
matches =
Enum.zip(target_chars, text_chars)
|> Enum.count(fn {t, c} -> t == c end)
# Normalize to 0.0-1.0
similarity = matches / String.length(@target)
{:ok, similarity}
end
def batch_evaluate(entities, context) do
results =
Enum.map(entities, fn entity ->
{:ok, score} = evaluate(entity, context)
{entity, score}
end)
{:ok, results}
end
@doc """
Run the hello world evolution example.
## Options
- `:population_size` - Size of the population (default: 100)
- `:generations` - Maximum generations (default: 300)
- `:mutation_rate` - Mutation rate (default: 0.3)
- `:crossover_rate` - Crossover rate (default: 0.8)
- `:elitism_rate` - Elitism rate (default: 0.02)
- `:target_fitness` - Stop when fitness reaches this value (default: 0.99)
- `:seed` - Initial population (default: 100 random strings)
- `:verbose` - Print progress (default: false)
## Examples
# Run with defaults
Jido.Evolve.Examples.HelloWorld.run()
# Run with custom settings
Jido.Evolve.Examples.HelloWorld.run(
population_size: 50,
mutation_rate: 0.6,
verbose: true
)
"""
def run(opts \\ []) do
population_size = Keyword.get(opts, :population_size, 100)
generations = Keyword.get(opts, :generations, 300)
mutation_rate = Keyword.get(opts, :mutation_rate, 0.3)
crossover_rate = Keyword.get(opts, :crossover_rate, 0.8)
elitism_rate = Keyword.get(opts, :elitism_rate, 0.02)
target_fitness = Keyword.get(opts, :target_fitness, 0.99)
seed = Keyword.get(opts, :seed, Enum.map(1..population_size, fn _ -> random_string() end))
verbose = Keyword.get(opts, :verbose, false)
# Create configuration with adaptive mutation
{:ok, config} =
Jido.Evolve.Config.new(
population_size: population_size,
generations: generations,
mutation_rate: mutation_rate,
crossover_rate: crossover_rate,
elitism_rate: elitism_rate,
selection_strategy: Jido.Evolve.Selection.Tournament,
mutation_strategy: Jido.Evolve.Mutation.AdaptiveText,
crossover_strategy: Jido.Evolve.Crossover.String,
termination_criteria: [target_fitness: target_fitness]
)
if verbose do
IO.puts("Starting evolution:")
IO.puts("Target: #{@target}")
IO.puts("Initial: #{Enum.join(seed, ", ")}")
IO.puts("Population size: #{population_size}")
IO.puts("Mutation rate: #{mutation_rate} (adaptive: 0.3 → 0.08 at fitness > 0.75)")
IO.puts("")
end
# Run evolution
result =
Jido.Evolve.evolve(
initial_population: seed,
config: config,
fitness: __MODULE__
)
|> Stream.with_index()
|> Stream.map(fn {state, generation} ->
if verbose and rem(generation, 10) == 0 do
IO.puts("Generation #{generation}: #{state.best_entity} (fitness: #{Float.round(state.best_score, 4)})")
end
state
end)
|> Stream.take_while(fn state ->
state.best_score < target_fitness and state.generation < generations
end)
|> Enum.to_list()
|> List.last()
if result do
if verbose do
IO.puts("")
IO.puts("Evolution completed!")
IO.puts("Final result: #{result.best_entity}")
IO.puts("Final fitness: #{Float.round(result.best_score, 6)}")
IO.puts("Generations: #{result.generation}")
end
%{
best_entity: result.best_entity,
best_score: result.best_score,
generation: result.generation,
target: @target,
success: result.best_score >= target_fitness
}
else
if verbose do
IO.puts("Evolution failed to converge")
end
%{
best_entity: nil,
best_score: 0.0,
generation: 0,
target: @target,
success: false
}
end
end
@doc """
Run a quick demo that prints progress.
"""
def demo do
IO.puts("Jido.Evolve Hello World Evolution Demo")
IO.puts("=" |> String.duplicate(40))
run(verbose: true)
end
defp random_string do
chars = String.graphemes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ,!?")
Enum.map_join(1..String.length(@target), "", fn _ -> Enum.random(chars) end)
end
end