Source code for metaheuristic_designer.survivor_selection_base

"""Base class for the Survivor Selection module."""

from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Optional, Callable
import inspect
import numpy as np
from .parametrizable_mixin import ParametrizableMixin
from .population import Population
from .utils import check_random_state, RNGLike


[docs] class SurvivorSelection(ParametrizableMixin, ABC): """Abstract base for all survivor selection methods. A survivor selection decides which individuals from the current population and the newly generated offspring will form the next generation. Subclasses must implement :meth:`select`. Parameters ---------- name : str, optional Display name for this selection method. preserves_order : bool, optional If ``True``, the order of individuals is kept (useful for one-to-one competition schemes). Default ``False``. random_state : RNGLike, optional Random number generator. **kwargs Additional keyword arguments stored as schedulable parameters. """ def __init__(self, name: Optional[str] = None, preserves_order: bool = False, random_state: Optional[RNGLike] = None, **kwargs): super().__init__() self.name = name self.preserves_order = preserves_order self.random_state = check_random_state(random_state) self.store_kwargs(**kwargs) self.last_selection_idx = None def __call__(self, population: Population, offspring: Population) -> Population: """Shorthand for :meth:`select`.""" return self.select(population, offspring)
[docs] def gather_params(self): """Return the current parameter dictionary (thin wrapper around :meth:`get_params`).""" return self.get_params()
[docs] @abstractmethod def select(self, population: Population, offspring: Population) -> Population: """ Takes a population with its offspring and returns the individuals that survive to produce the next generation. Parameters ---------- population: Population Population of individuals that will be selected. offspring: Population Newly generated individuals to be selected. Returns ------- selected: Population Population containing only the selected survivors. """
[docs] def get_state(self) -> dict: """Return a dictionary with the selection method's configuration. Returns ------- dict Keys include ``class_name``, ``name``, and all current parameters. """ data = {"class_name": self.__class__.__name__, "name": self.name, **self.get_params()} return data
[docs] class NullSurvivorSelection(SurvivorSelection): """Null survivor selection, offspring replace parents entirely. This is the identity element for generational replacement: all parents are discarded and all offspring survive. The population size must be maintained by the offspring. Parameters ---------- name : str, optional Display name. Default ``"Nothing"``. **kwargs Keyword arguments forwarded to :class:`SurvivorSelection`. """ def __init__(self, name: Optional[str] = "Nothing", **kwargs): super().__init__(name, preserves_order=True, random_state=None, **kwargs)
[docs] def select(self, population: Population, offspring: Population) -> Population: self.last_selection_idx = np.arange(population.population_size, population.population_size + offspring.population_size) offspring = offspring.update_best_from_parents(population) return offspring
[docs] class SurvivorSelectionFromLambda(SurvivorSelection): """Survivor selection that wraps a user-supplied function. The function receives the parent population, the offspring population, a random state, and any stored keyword arguments, and must return an array of indices into the concatenated pool. Parameters ---------- selection_fn : callable A function ``(parents, offspring, random_state, **kwargs) -> indices``. name : str, optional Display name (defaults to the function's ``__name__``). preserves_order : bool, optional See :class:`SurvivorSelection`. random_state : RNGLike, optional Random number generator. **kwargs Keyword arguments forwarded to :class:`SurvivorSelection`. """ def __init__( self, selection_fn: Callable, name: Optional[str] = None, preserves_order: bool = False, random_state: Optional[RNGLike] = None, **kwargs ): if name is None: name = selection_fn.__name__ if hasattr(selection_fn, "__name__") else "Custom survivor selection" self.selection_fn = selection_fn super().__init__(name, preserves_order=preserves_order, random_state=random_state, **kwargs) @staticmethod def _validate_function(operator_fn: Callable): operator_sig = inspect.signature(operator_fn) count = 0 for p in operator_sig.parameters.values(): if p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD: count += 1 elif p.kind == inspect.Parameter.VAR_POSITIONAL: return required_min_count = 3 if count < required_min_count: raise TypeError(f"The function should have at least {required_min_count} positional arguments since it is.")
[docs] def select(self, population: Population, offspring: Population) -> Population: selected_idx = self.selection_fn(population, offspring, self.random_state, **self.current_kwargs) return Population.join_populations(population, offspring).take_selection(selected_idx)