Best-of-N (attempt-counted)
Best-of-N that rotates the parent every N attempts — failed/invalid tries spend the budget too.
"""Best-of-N-Attempts SelectionPolicy component — spend one budget unit per *attempt*, valid or not.
The attempt-counted variant of :class:`~galapagos.scaffolds.best_of_n.selection_policy.BestOfNPolicy`.
Where the faithful ``best_of_n`` advances the parent-reuse counter only on a valid child (in
``observe``), this variant advances it once per :meth:`select` — so a parse/eval failure still spends
one of the parent's N attempts and the parent rotates on a fixed cadence of N iterations.
"""
from __future__ import annotations
from ...records import RunState, Selection
from ..best_of_n.selection_policy import BestOfNPolicy
class BestOfNAttemptsPolicy(BestOfNPolicy):
"""Reuse one parent for N consecutive *attempts* (iterations), then switch to the global best.
Only the counter-increment site differs from :class:`BestOfNPolicy`: the budget is spent at
selection time, so failed/invalid attempts count too. Parent choice and inspiration sampling are
inherited unchanged (``_choose_parent`` / ``_inspirations``).
"""
def select(self, population, state: RunState | None = None) -> Selection:
members = population.all()
if not members:
raise RuntimeError("cannot select from an empty population")
parent = self._choose_parent(members)
self.uses += 1 # attempt-counted: spend the budget now, regardless of the child's outcome
return Selection(parent=parent, inspirations=self._inspirations(population, parent), pool=members)
def observe(self, genome, state: RunState | None = None) -> None:
pass # budget already spent in select(); nothing to do on the scored child