Source code for metaheuristic_designer.parent_selection_base

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

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


[docs] class ParentSelection(ParametrizableMixin, ABC): """Abstract base for all parent selection methods. A parent selection chooses which individuals from the current population will be used to generate offspring. Subclasses must implement :meth:`select`, which returns a new :class:`Population` containing only the selected individuals. Parameters ---------- name : str, optional Display name for this selection method. amount : int, optional Default number of individuals to select. Can be overridden at call time. random_state : RNGLike, optional Random number generator. **kwargs Additional keyword arguments stored as schedulable parameters. """ def __init__(self, name: Optional[str] = None, amount: Optional[int] = None, random_state: Optional[RNGLike] = None, **kwargs): super().__init__() self.name = name self.random_state = check_random_state(random_state) self.store_kwargs(amount=amount, **kwargs) self.last_selection_idx = None def __call__(self, population: Population, amount: Optional[int] = None) -> Population: """Shorthand for :meth:`select`.""" return self.select(population, amount)
[docs] def gather_params(self) -> dict: """Return the current parameter dictionary (thin wrapper around :meth:`get_params`).""" return self.get_params()
[docs] @abstractmethod def select(self, population: Population, amount: Optional[int] = None) -> 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 List of selected individuals. """
[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 NullParentSelection(ParentSelection): """Null parent selection, returns the whole population unchanged. This is the identity element: no individuals are filtered out. Useful when the algorithm does not require a parent selection step (e.g., random search or certain evolution strategies). Parameters ---------- name : str, optional Display name. Default ``"Nothing"``. **kwargs Keyword arguments forwarded to :class:`ParentSelection`. """ def __init__(self, name: Optional[str] = "Nothing", **kwargs): super().__init__(name, amount=None, **kwargs)
[docs] def select(self, population: Population, amount: Optional[int] = None) -> Population: self.last_selection_idx = np.arange(population.population_size) return population.take_selection(self.last_selection_idx)
[docs] class ParentSelectionFromLambda(ParentSelection): """Parent selection that wraps a user-supplied function. The function receives the population, the number of individuals to select, a random state, and any stored keyword arguments, and must return an array of selected indices. Parameters ---------- selection_fn : callable A function ``(population, amount, random_state, **kwargs) -> indices``. name : str, optional Display name (defaults to the function's ``__name__``). amount : int, optional Default number of individuals to select. random_state : RNGLike, optional Random number generator. **kwargs Keyword arguments forwarded to :class:`ParentSelection`. """ def __init__( self, selection_fn: Callable, name: Optional[str] = None, amount: Optional[int] = None, random_state: Optional[RNGLike] = None, **kwargs ): if name is None: name = selection_fn.__name__ if hasattr(selection_fn, "__name__") else "Custom parent selection" super().__init__(name=name, amount=amount, random_state=random_state, **kwargs) self.selection_fn = selection_fn @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, amount: Optional[int] = None) -> Population: if amount is None: if self.current_kwargs["amount"] is None: amount = population.population_size else: amount = self.current_kwargs["amount"] params = self.get_params() params.pop("amount", None) self.last_selection_idx = self.selection_fn(population, amount, self.random_state, **params) return population.take_selection(self.last_selection_idx)