Genetic Algorithm¶
genetic_algorithm(problem, *, stop, rng=None, population_size=10, selection=random_weighted_selection, crossover=single_point_crossover, mutation=inter_mutation, repair=None, max_tries=1000)
¶
Evolve a population of problem solutions until stop is met.
Each generation breeds population_size children: selection
picks a pair of parents and crossover breeds them, repeated until
the population is full. Every child is mutated (mutation is retried
up to max_tries times for a feasible mutant, else the child is kept
as is), then any child still infeasible is passed through repair if
given, and replaced by a fresh feasible genome if that does not fix it.
After evaluation the best genome so far replaces the worst child if it
is better (elitism), so it is never lost and never re-evaluated: each
generation costs population_size evaluations (plus any made by a
custom selection on genomes outside the population).
Operator contracts (bind knobs with functools.partial)::
selection(population, scores, rng, k) -> k parents # GA: k=2
crossover(parent1, parent2, rng) -> (child1, child2)
mutation(genome, rng) -> genome
scores are oriented (lower is better for either direction), so a
selection never needs the problem's direction.
stop is checked once per generation, so max_evaluations may be
exceeded by up to one generation of evaluations.
The result's history has one dict per generation, the initial
population first (so len(history) == iterations + 1)::
{'best': float, 'mean': float, 'worst': float, 'solution': Genome,
'best_so_far': float}
best/solution are that generation's best; best_so_far is
the best value seen up to and including that generation (the same
measure as SA's float history). With elitism the two values coincide.
metadata holds evaluations (objective calls), termination
('stop') and state (the final State), as for every heuristic
built on core.run.
problem.generate is called with the run's rng.