Source code for metaheuristic_designer.strategies.shuffled_population_strategy

"""
Strategy where offspring size differs from population size (μ+λ / μ,λ style).
"""

from __future__ import annotations
import logging
from typing import Optional

from ..population import Population
from ..objective_function import ObjectiveFunc
from ..initializer import Initializer
from ..parent_selection_base import ParentSelection
from ..survivor_selection_base import SurvivorSelection
from ..parent_selection import create_parent_selection
from ..search_strategy import SearchStrategy
from ..operator import Operator
from ..utils import check_rng, RNGLike
from ..schedulable_parameter import SchedulableParameter

logger = logging.getLogger(__name__)


[docs] class ShuffledPopulationStrategy(SearchStrategy): """ Population-based strategy with separate parent and offspring sizes. This is the base for (μ+λ) and (μ,λ) Evolution Strategies, GAs, and similar algorithms. The number of parents selected and the number of offspring generated can be configured independently. Parameters ---------- initializer : Initializer Population initializer. operator : Operator Perturbation operator. parent_sel : ParentSelection, optional Parent selection method. survivor_sel : SurvivorSelection, optional Survivor selection method. offspring_size : int or SchedulableParameter, optional Number of offspring to generate. Defaults to the initializer's population size. shuffle_with_replacement : bool, optional If ``True``, shuffle the parent pool with replacement; otherwise without replacement (default ``False``). name : str, optional Display name. rng : RNGLike, optional Random number generator. \\*\\*kwargs Forwarded to :class:`SearchStrategy`. """ def __init__( self, initializer: Initializer, operator: Operator = None, parent_sel: Optional[ParentSelection] = None, survivor_sel: Optional[SurvivorSelection] = None, offspring_size: Optional[int | SchedulableParameter] = None, shuffle_with_replacement: bool = False, name: str = "Variable Population Evolution", rng: Optional[RNGLike] = None, **kwargs, ): # We need to set up the random state beforehand to handle the initializer correctly rng = check_rng(rng) self.using_custom_offspring_size = offspring_size is not None if offspring_size is None: offspring_size = initializer.population_size self.offspring_size = offspring_size self.shuffle_with_replacement = shuffle_with_replacement super().__init__( initializer, operator=operator, parent_sel=parent_sel, survivor_sel=survivor_sel, name=name, rng=rng, # Forced kwargs offspring_size=offspring_size, **kwargs, ) @property def initializer(self) -> Initializer: return self._initializer @initializer.setter def initializer(self, new_initializer: Initializer): """Update the offspring size and shuffler when the initializer changes. Parameters ---------- new_initializer : Initializer The new initializer. """ if not self.using_custom_offspring_size: self.update_kwargs(offspring_size=new_initializer.population_size) if hasattr(self.params, "offspring_size"): offspring_size = self.params.offspring_size else: offspring_size = self.offspring_size if self.shuffle_with_replacement: self.population_shuffler = create_parent_selection("random_with_replacement", amount=offspring_size, rng=self.rng) else: self.population_shuffler = create_parent_selection("random_without_replacement", amount=offspring_size, rng=self.rng) self._initializer = new_initializer
[docs] def step(self, prev_population: Population, objfunc: ObjectiveFunc) -> Population: population = self.parent_sel.select(prev_population) # implicit copy population = self.population_shuffler(population) population = self.operator.evolve(population) population = objfunc.repair_population(population) population = objfunc.calculate_fitness(population) population = self.survivor_sel.select(population=prev_population, offspring=population) return population