Source code for metaheuristic_designer.strategies.variable_population
"""
Strategy where offspring size differs from population size (μ+λ / μ,λ style).
"""
from __future__ import annotations
import logging
from typing import Optional
from ..population import Population
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_random_state, RNGLike
from ..schedulable_parameter import SchedulableParameter
logger = logging.getLogger(__name__)
[docs]
class VariablePopulation(SearchStrategy):
"""
Population-based strategy with separate parent and offspring sizes.
This is the base for (μ+λ) and (μ,λ) Evolution Strategies, GAs
with elitism, 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.
random_state : RNGLike, optional
Random number generator.
**kwargs
Forwarded to :class:`SearchStrategy`.
"""
def __init__(
self,
initializer: Initializer,
operator: Operator,
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",
random_state: Optional[RNGLike] = None,
**kwargs,
):
# We need to set up the random state beforehand to handle the initializer correctly
self.random_state = check_random_state(random_state)
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,
random_state=random_state,
# 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, random_state=self.random_state)
else:
self.population_shuffler = create_parent_selection("random_without_replacement", amount=offspring_size, random_state=self.random_state)
self._initializer = new_initializer
[docs]
def select_parents(self, population: Population) -> Population:
"""Select parents, then optionally shuffle the pool.
Parameters
----------
population : Population
Current population.
Returns
-------
Population
The (possibly shuffled) selected parents.
"""
next_population = super().select_parents(population)
next_population = self.population_shuffler(next_population)
return next_population