Skip to content

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.