Skip to content

Selection

selection

Selection strategies for evolution candidates, components, and evaluation.

Contains candidate selection (Pareto, greedy, epsilon-greedy), component selection (round-robin, all), and evaluation policy (full, subset) implementations.

Anticipated growth: tournament selection, adaptive selection strategies, custom evaluation scheduling.

ATTRIBUTE DESCRIPTION
ParetoCandidateSelector

Pareto frontier sampling selector.

CurrentBestCandidateSelector

Greedy best-average selector.

EpsilonGreedyCandidateSelector

Epsilon-greedy exploration selector.

create_candidate_selector

Factory for candidate selectors.

TYPE: CandidateSelectorProtocol

RoundRobinComponentSelector

Cycles through components sequentially.

TYPE: CandidateSelectorProtocol

AllComponentSelector

Selects all components every time.

TYPE: CandidateSelectorProtocol

create_component_selector

Factory for component selectors.

TYPE: ComponentSelectorProtocol

FullEvaluationPolicy

Scores all validation examples every iteration.

TYPE: ComponentSelectorProtocol

SubsetEvaluationPolicy

Scores a configurable subset with round-robin coverage.

TYPE: ComponentSelectorProtocol

Examples:

Create a candidate selector using the factory:

from gepa_adk.adapters.selection import create_candidate_selector

selector = create_candidate_selector("pareto")

Use a component selector and evaluation policy:

from gepa_adk.adapters.selection import (
    RoundRobinComponentSelector,
    SubsetEvaluationPolicy,
)

component_selector = RoundRobinComponentSelector()
eval_policy = SubsetEvaluationPolicy(subset_size=5)
See Also
Note

This package unifies candidate, component, and evaluation selection strategies.

CurrentBestCandidateSelector

Always select the candidate with the highest average score.

Note

A greedy selector always exploits the best-average candidate.

Examples:

selector = CurrentBestCandidateSelector()
candidate_idx = await selector.select_candidate(state)
Source code in src/gepa_adk/adapters/selection/candidate_selector.py
class CurrentBestCandidateSelector:
    """Always select the candidate with the highest average score.

    Note:
        A greedy selector always exploits the best-average candidate.

    Examples:
        ```python
        selector = CurrentBestCandidateSelector()
        candidate_idx = await selector.select_candidate(state)
        ```
    """

    async def select_candidate(self, state: ParetoState) -> int:
        """Return the best-average candidate index.

        Args:
            state: Current evolution state with Pareto tracking.

        Returns:
            Selected candidate index.

        Raises:
            NoCandidateAvailableError: If no candidates are available.

        Examples:
            ```python
            candidate_idx = await selector.select_candidate(state)
            ```
        """
        if state.best_average_idx is None:
            raise NoCandidateAvailableError("No candidates available for selection")
        return state.best_average_idx

select_candidate async

select_candidate(state: ParetoState) -> int

Return the best-average candidate index.

PARAMETER DESCRIPTION
state

Current evolution state with Pareto tracking.

TYPE: ParetoState

RETURNS DESCRIPTION
int

Selected candidate index.

RAISES DESCRIPTION
NoCandidateAvailableError

If no candidates are available.

Examples:

candidate_idx = await selector.select_candidate(state)
Source code in src/gepa_adk/adapters/selection/candidate_selector.py
async def select_candidate(self, state: ParetoState) -> int:
    """Return the best-average candidate index.

    Args:
        state: Current evolution state with Pareto tracking.

    Returns:
        Selected candidate index.

    Raises:
        NoCandidateAvailableError: If no candidates are available.

    Examples:
        ```python
        candidate_idx = await selector.select_candidate(state)
        ```
    """
    if state.best_average_idx is None:
        raise NoCandidateAvailableError("No candidates available for selection")
    return state.best_average_idx

EpsilonGreedyCandidateSelector

Epsilon-greedy selection balancing exploration and exploitation.

Note

A mixed strategy sometimes explores and otherwise exploits the best.

ATTRIBUTE DESCRIPTION
_epsilon

Exploration probability.

TYPE: float

_rng

RNG for exploration decisions.

TYPE: Random

Examples:

selector = EpsilonGreedyCandidateSelector(epsilon=0.1, rng=random.Random(7))
candidate_idx = await selector.select_candidate(state)
Source code in src/gepa_adk/adapters/selection/candidate_selector.py
class EpsilonGreedyCandidateSelector:
    """Epsilon-greedy selection balancing exploration and exploitation.

    Note:
        A mixed strategy sometimes explores and otherwise exploits the best.

    Attributes:
        _epsilon (float): Exploration probability.
        _rng (random.Random): RNG for exploration decisions.

    Examples:
        ```python
        selector = EpsilonGreedyCandidateSelector(epsilon=0.1, rng=random.Random(7))
        candidate_idx = await selector.select_candidate(state)
        ```
    """

    def __init__(self, epsilon: float, rng: random.Random | None = None) -> None:
        """Initialize the selector.

        Args:
            epsilon: Probability of random exploration.
            rng: Optional random number generator for reproducibility.

        Raises:
            ConfigurationError: If epsilon is outside [0.0, 1.0].
        """
        if not 0.0 <= epsilon <= 1.0:
            raise ConfigurationError(
                "epsilon must be between 0.0 and 1.0",
                field="epsilon",
                value=epsilon,
                constraint="0.0 <= epsilon <= 1.0",
            )
        self._epsilon = epsilon
        self._rng = rng or random.Random()

    async def select_candidate(self, state: ParetoState) -> int:
        """Select a candidate using epsilon-greedy strategy.

        Args:
            state: Current evolution state with Pareto tracking.

        Returns:
            Selected candidate index.

        Raises:
            NoCandidateAvailableError: If no candidates are available.

        Examples:
            ```python
            candidate_idx = await selector.select_candidate(state)
            ```
        """
        if not state.candidates:
            raise NoCandidateAvailableError("No candidates available for selection")

        if self._rng.random() < self._epsilon:
            return self._rng.randint(0, len(state.candidates) - 1)

        if state.best_average_idx is None:
            raise NoCandidateAvailableError("No candidates available for selection")

        return state.best_average_idx

__init__

__init__(epsilon: float, rng: Random | None = None) -> None

Initialize the selector.

PARAMETER DESCRIPTION
epsilon

Probability of random exploration.

TYPE: float

rng

Optional random number generator for reproducibility.

TYPE: Random | None DEFAULT: None

RAISES DESCRIPTION
ConfigurationError

If epsilon is outside [0.0, 1.0].

Source code in src/gepa_adk/adapters/selection/candidate_selector.py
def __init__(self, epsilon: float, rng: random.Random | None = None) -> None:
    """Initialize the selector.

    Args:
        epsilon: Probability of random exploration.
        rng: Optional random number generator for reproducibility.

    Raises:
        ConfigurationError: If epsilon is outside [0.0, 1.0].
    """
    if not 0.0 <= epsilon <= 1.0:
        raise ConfigurationError(
            "epsilon must be between 0.0 and 1.0",
            field="epsilon",
            value=epsilon,
            constraint="0.0 <= epsilon <= 1.0",
        )
    self._epsilon = epsilon
    self._rng = rng or random.Random()

select_candidate async

select_candidate(state: ParetoState) -> int

Select a candidate using epsilon-greedy strategy.

PARAMETER DESCRIPTION
state

Current evolution state with Pareto tracking.

TYPE: ParetoState

RETURNS DESCRIPTION
int

Selected candidate index.

RAISES DESCRIPTION
NoCandidateAvailableError

If no candidates are available.

Examples:

candidate_idx = await selector.select_candidate(state)
Source code in src/gepa_adk/adapters/selection/candidate_selector.py
async def select_candidate(self, state: ParetoState) -> int:
    """Select a candidate using epsilon-greedy strategy.

    Args:
        state: Current evolution state with Pareto tracking.

    Returns:
        Selected candidate index.

    Raises:
        NoCandidateAvailableError: If no candidates are available.

    Examples:
        ```python
        candidate_idx = await selector.select_candidate(state)
        ```
    """
    if not state.candidates:
        raise NoCandidateAvailableError("No candidates available for selection")

    if self._rng.random() < self._epsilon:
        return self._rng.randint(0, len(state.candidates) - 1)

    if state.best_average_idx is None:
        raise NoCandidateAvailableError("No candidates available for selection")

    return state.best_average_idx

ParetoCandidateSelector

Sample from the Pareto front proportional to leadership frequency.

Note

A Pareto selector emphasizes candidates that lead more examples.

ATTRIBUTE DESCRIPTION
_rng

RNG for sampling candidates.

TYPE: Random

Examples:

selector = ParetoCandidateSelector(rng=random.Random(42))
candidate_idx = await selector.select_candidate(state)
Source code in src/gepa_adk/adapters/selection/candidate_selector.py
class ParetoCandidateSelector:
    """Sample from the Pareto front proportional to leadership frequency.

    Note:
        A Pareto selector emphasizes candidates that lead more examples.

    Attributes:
        _rng (random.Random): RNG for sampling candidates.

    Examples:
        ```python
        selector = ParetoCandidateSelector(rng=random.Random(42))
        candidate_idx = await selector.select_candidate(state)
        ```
    """

    def __init__(self, rng: random.Random | None = None) -> None:
        """Initialize the selector.

        Args:
            rng: Optional random number generator for reproducibility.
        """
        self._rng = rng or random.Random()

    async def select_candidate(self, state: ParetoState) -> int:
        """Select a candidate index from the Pareto frontier.

        Args:
            state: Current evolution state with Pareto tracking.

        Returns:
            Selected candidate index.

        Raises:
            NoCandidateAvailableError: If no candidates or leaders exist.

        Examples:
            ```python
            candidate_idx = await selector.select_candidate(state)
            ```
        """
        if not state.candidates:
            raise NoCandidateAvailableError("No candidates available for selection")

        weights = state.frontier.get_selection_weights()
        if not weights:
            raise NoCandidateAvailableError("Pareto frontier is empty")

        sampling_list = [
            candidate_idx
            for candidate_idx, weight in weights.items()
            for _ in range(weight)
        ]
        if not sampling_list:
            raise NoCandidateAvailableError("Pareto frontier has no leaders")

        return self._rng.choice(sampling_list)

__init__

__init__(rng: Random | None = None) -> None

Initialize the selector.

PARAMETER DESCRIPTION
rng

Optional random number generator for reproducibility.

TYPE: Random | None DEFAULT: None

Source code in src/gepa_adk/adapters/selection/candidate_selector.py
def __init__(self, rng: random.Random | None = None) -> None:
    """Initialize the selector.

    Args:
        rng: Optional random number generator for reproducibility.
    """
    self._rng = rng or random.Random()

select_candidate async

select_candidate(state: ParetoState) -> int

Select a candidate index from the Pareto frontier.

PARAMETER DESCRIPTION
state

Current evolution state with Pareto tracking.

TYPE: ParetoState

RETURNS DESCRIPTION
int

Selected candidate index.

RAISES DESCRIPTION
NoCandidateAvailableError

If no candidates or leaders exist.

Examples:

candidate_idx = await selector.select_candidate(state)
Source code in src/gepa_adk/adapters/selection/candidate_selector.py
async def select_candidate(self, state: ParetoState) -> int:
    """Select a candidate index from the Pareto frontier.

    Args:
        state: Current evolution state with Pareto tracking.

    Returns:
        Selected candidate index.

    Raises:
        NoCandidateAvailableError: If no candidates or leaders exist.

    Examples:
        ```python
        candidate_idx = await selector.select_candidate(state)
        ```
    """
    if not state.candidates:
        raise NoCandidateAvailableError("No candidates available for selection")

    weights = state.frontier.get_selection_weights()
    if not weights:
        raise NoCandidateAvailableError("Pareto frontier is empty")

    sampling_list = [
        candidate_idx
        for candidate_idx, weight in weights.items()
        for _ in range(weight)
    ]
    if not sampling_list:
        raise NoCandidateAvailableError("Pareto frontier has no leaders")

    return self._rng.choice(sampling_list)

AllComponentSelector

Selects all available components for simultaneous update.

This selector returns the full list of components every time, enabling simultaneous evolution of all parts of the candidate.

Examples:

selector = AllComponentSelector()
all_comps = await selector.select_components(["a", "b"], 1, 0)
# Returns ["a", "b"]
Note

Always returns all components, enabling comprehensive mutations across the entire candidate in a single iteration.

Source code in src/gepa_adk/adapters/selection/component_selector.py
class AllComponentSelector:
    """Selects all available components for simultaneous update.

    This selector returns the full list of components every time, enabling
    simultaneous evolution of all parts of the candidate.

    Examples:
        ```python
        selector = AllComponentSelector()
        all_comps = await selector.select_components(["a", "b"], 1, 0)
        # Returns ["a", "b"]
        ```

    Note:
        Always returns all components, enabling comprehensive mutations
        across the entire candidate in a single iteration.
    """

    async def select_components(
        self, components: list[str], iteration: int, candidate_idx: int
    ) -> list[str]:
        """Select all components to update.

        Args:
            components: List of available component keys.
            iteration: Current global iteration number (unused).
            candidate_idx: Index of the candidate being evolved (unused).

        Returns:
            List containing all component keys.

        Raises:
            ValueError: If components list is empty.

        Examples:
            ```python
            selected = await selector.select_components(["a", "b"], 1, 0)
            ```

        Note:
            Outputs the complete component list unchanged, enabling
            simultaneous evolution of all candidate parts.
        """
        if not components:
            raise ValueError("No components provided for selection")

        return list(components)

select_components async

select_components(
    components: list[str],
    iteration: int,
    candidate_idx: int,
) -> list[str]

Select all components to update.

PARAMETER DESCRIPTION
components

List of available component keys.

TYPE: list[str]

iteration

Current global iteration number (unused).

TYPE: int

candidate_idx

Index of the candidate being evolved (unused).

TYPE: int

RETURNS DESCRIPTION
list[str]

List containing all component keys.

RAISES DESCRIPTION
ValueError

If components list is empty.

Examples:

selected = await selector.select_components(["a", "b"], 1, 0)
Note

Outputs the complete component list unchanged, enabling simultaneous evolution of all candidate parts.

Source code in src/gepa_adk/adapters/selection/component_selector.py
async def select_components(
    self, components: list[str], iteration: int, candidate_idx: int
) -> list[str]:
    """Select all components to update.

    Args:
        components: List of available component keys.
        iteration: Current global iteration number (unused).
        candidate_idx: Index of the candidate being evolved (unused).

    Returns:
        List containing all component keys.

    Raises:
        ValueError: If components list is empty.

    Examples:
        ```python
        selected = await selector.select_components(["a", "b"], 1, 0)
        ```

    Note:
        Outputs the complete component list unchanged, enabling
        simultaneous evolution of all candidate parts.
    """
    if not components:
        raise ValueError("No components provided for selection")

    return list(components)

RoundRobinComponentSelector

Selects components in a round-robin fashion.

This selector cycles through the list of components one by one, maintaining state per candidate index to ensure consistent rotation.

ATTRIBUTE DESCRIPTION
_next_index

Mapping of candidate_idx to next component index.

TYPE: dict[int, int]

Examples:

selector = RoundRobinComponentSelector()
# First call selects first component
c1 = await selector.select_components(["a", "b"], 1, 0)  # ["a"]
# Second call selects second component
c2 = await selector.select_components(["a", "b"], 2, 0)  # ["b"]
Note

Alternates through components sequentially, ensuring balanced evolution across all candidate parts.

Source code in src/gepa_adk/adapters/selection/component_selector.py
class RoundRobinComponentSelector:
    """Selects components in a round-robin fashion.

    This selector cycles through the list of components one by one, maintaining
    state per candidate index to ensure consistent rotation.

    Attributes:
        _next_index (dict[int, int]): Mapping of candidate_idx to next component index.

    Examples:
        ```python
        selector = RoundRobinComponentSelector()
        # First call selects first component
        c1 = await selector.select_components(["a", "b"], 1, 0)  # ["a"]
        # Second call selects second component
        c2 = await selector.select_components(["a", "b"], 2, 0)  # ["b"]
        ```

    Note:
        Alternates through components sequentially, ensuring balanced evolution
        across all candidate parts.
    """

    def __init__(self) -> None:
        """Initialize the round-robin selector.

        Note:
            Creates empty index tracking dictionary for per-candidate rotation state.
        """
        self._next_index: dict[int, int] = defaultdict(int)

    async def select_components(
        self, components: list[str], iteration: int, candidate_idx: int
    ) -> list[str]:
        """Select a single component to update using round-robin logic.

        Args:
            components: List of available component keys.
            iteration: Current global iteration number (unused by this strategy).
            candidate_idx: Index of the candidate being evolved.

        Returns:
            List containing the single selected component key.

        Raises:
            ValueError: If components list is empty.

        Examples:
            ```python
            selected = await selector.select_components(["a", "b"], 1, 0)
            ```

        Note:
            Outputs one component per call, advancing the rotation index for
            the specified candidate.
        """
        if not components:
            raise ValueError("No components provided for selection")

        # Get current index for this candidate
        current_idx = self._next_index[candidate_idx]

        # Select component
        selected = components[current_idx % len(components)]

        # Advance index for next time
        self._next_index[candidate_idx] = (current_idx + 1) % len(components)

        return [selected]

__init__

__init__() -> None

Initialize the round-robin selector.

Note

Creates empty index tracking dictionary for per-candidate rotation state.

Source code in src/gepa_adk/adapters/selection/component_selector.py
def __init__(self) -> None:
    """Initialize the round-robin selector.

    Note:
        Creates empty index tracking dictionary for per-candidate rotation state.
    """
    self._next_index: dict[int, int] = defaultdict(int)

select_components async

select_components(
    components: list[str],
    iteration: int,
    candidate_idx: int,
) -> list[str]

Select a single component to update using round-robin logic.

PARAMETER DESCRIPTION
components

List of available component keys.

TYPE: list[str]

iteration

Current global iteration number (unused by this strategy).

TYPE: int

candidate_idx

Index of the candidate being evolved.

TYPE: int

RETURNS DESCRIPTION
list[str]

List containing the single selected component key.

RAISES DESCRIPTION
ValueError

If components list is empty.

Examples:

selected = await selector.select_components(["a", "b"], 1, 0)
Note

Outputs one component per call, advancing the rotation index for the specified candidate.

Source code in src/gepa_adk/adapters/selection/component_selector.py
async def select_components(
    self, components: list[str], iteration: int, candidate_idx: int
) -> list[str]:
    """Select a single component to update using round-robin logic.

    Args:
        components: List of available component keys.
        iteration: Current global iteration number (unused by this strategy).
        candidate_idx: Index of the candidate being evolved.

    Returns:
        List containing the single selected component key.

    Raises:
        ValueError: If components list is empty.

    Examples:
        ```python
        selected = await selector.select_components(["a", "b"], 1, 0)
        ```

    Note:
        Outputs one component per call, advancing the rotation index for
        the specified candidate.
    """
    if not components:
        raise ValueError("No components provided for selection")

    # Get current index for this candidate
    current_idx = self._next_index[candidate_idx]

    # Select component
    selected = components[current_idx % len(components)]

    # Advance index for next time
    self._next_index[candidate_idx] = (current_idx + 1) % len(components)

    return [selected]

FullEvaluationPolicy

Evaluation policy that scores all validation examples every iteration.

This is the default evaluation policy, providing complete visibility into solution performance across all validation examples.

Note

Always returns all valset IDs, ensuring complete evaluation coverage each iteration.

Examples:

policy = FullEvaluationPolicy()
batch = policy.get_eval_batch([0, 1, 2, 3, 4], state)
# Returns: [0, 1, 2, 3, 4]
Source code in src/gepa_adk/adapters/selection/evaluation_policy.py
class FullEvaluationPolicy:
    """Evaluation policy that scores all validation examples every iteration.

    This is the default evaluation policy, providing complete visibility
    into solution performance across all validation examples.

    Note:
        Always returns all valset IDs, ensuring complete evaluation coverage
        each iteration.

    Examples:
        ```python
        policy = FullEvaluationPolicy()
        batch = policy.get_eval_batch([0, 1, 2, 3, 4], state)
        # Returns: [0, 1, 2, 3, 4]
        ```
    """

    def get_eval_batch(
        self,
        valset_ids: Sequence[int],
        state: ParetoState,
        target_candidate_idx: int | None = None,
    ) -> list[int]:
        """Return all validation example indices.

        Args:
            valset_ids (Sequence[int]): All available validation example indices.
            state (ParetoState): Current evolution state (unused for full evaluation).
            target_candidate_idx (int | None): Optional candidate being evaluated
                (unused).

        Returns:
            list[int]: List of all valset_ids.

        Note:
            Outputs the complete valset for comprehensive evaluation coverage.

        Examples:
            ```python
            policy = FullEvaluationPolicy()
            batch = policy.get_eval_batch([0, 1, 2], state)
            assert batch == [0, 1, 2]
            ```
        """
        return list(valset_ids)

    def get_best_candidate(self, state: ParetoState) -> int:
        """Return index of candidate with highest average score.

        Args:
            state: Current evolution state with candidate scores.

        Returns:
            Index of best performing candidate.

        Raises:
            NoCandidateAvailableError: If state has no candidates.

        Note:
            Outputs the candidate index with the highest mean score across
            all evaluated examples.

        Examples:
            ```python
            policy = FullEvaluationPolicy()
            best_idx = policy.get_best_candidate(state)
            ```
        """
        if not state.candidates:
            raise NoCandidateAvailableError("No candidates available")

        best_idx = None
        best_score = float("-inf")
        for candidate_idx in range(len(state.candidates)):
            score = self.get_valset_score(candidate_idx, state)
            if score > best_score:
                best_score = score
                best_idx = candidate_idx

        if best_idx is None:
            raise NoCandidateAvailableError("No scored candidates available")
        return best_idx

    def get_valset_score(self, candidate_idx: int, state: ParetoState) -> float:
        """Return mean score across all evaluated examples for a candidate.

        Args:
            candidate_idx (int): Index of candidate to score.
            state (ParetoState): Current evolution state.

        Returns:
            float: Mean score across all examples, or float('-inf') if no scores.

        Note:
            Outputs the arithmetic mean of all scores for the candidate,
            or negative infinity if no scores exist.

        Examples:
            ```python
            policy = FullEvaluationPolicy()
            score = policy.get_valset_score(0, state)
            ```
        """
        scores = state.candidate_scores.get(candidate_idx)
        if not scores:
            return float("-inf")
        return fmean(scores.values())

get_eval_batch

get_eval_batch(
    valset_ids: Sequence[int],
    state: ParetoState,
    target_candidate_idx: int | None = None,
) -> list[int]

Return all validation example indices.

PARAMETER DESCRIPTION
valset_ids

All available validation example indices.

TYPE: Sequence[int]

state

Current evolution state (unused for full evaluation).

TYPE: ParetoState

target_candidate_idx

Optional candidate being evaluated (unused).

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
list[int]

list[int]: List of all valset_ids.

Note

Outputs the complete valset for comprehensive evaluation coverage.

Examples:

policy = FullEvaluationPolicy()
batch = policy.get_eval_batch([0, 1, 2], state)
assert batch == [0, 1, 2]
Source code in src/gepa_adk/adapters/selection/evaluation_policy.py
def get_eval_batch(
    self,
    valset_ids: Sequence[int],
    state: ParetoState,
    target_candidate_idx: int | None = None,
) -> list[int]:
    """Return all validation example indices.

    Args:
        valset_ids (Sequence[int]): All available validation example indices.
        state (ParetoState): Current evolution state (unused for full evaluation).
        target_candidate_idx (int | None): Optional candidate being evaluated
            (unused).

    Returns:
        list[int]: List of all valset_ids.

    Note:
        Outputs the complete valset for comprehensive evaluation coverage.

    Examples:
        ```python
        policy = FullEvaluationPolicy()
        batch = policy.get_eval_batch([0, 1, 2], state)
        assert batch == [0, 1, 2]
        ```
    """
    return list(valset_ids)

get_best_candidate

get_best_candidate(state: ParetoState) -> int

Return index of candidate with highest average score.

PARAMETER DESCRIPTION
state

Current evolution state with candidate scores.

TYPE: ParetoState

RETURNS DESCRIPTION
int

Index of best performing candidate.

RAISES DESCRIPTION
NoCandidateAvailableError

If state has no candidates.

Note

Outputs the candidate index with the highest mean score across all evaluated examples.

Examples:

policy = FullEvaluationPolicy()
best_idx = policy.get_best_candidate(state)
Source code in src/gepa_adk/adapters/selection/evaluation_policy.py
def get_best_candidate(self, state: ParetoState) -> int:
    """Return index of candidate with highest average score.

    Args:
        state: Current evolution state with candidate scores.

    Returns:
        Index of best performing candidate.

    Raises:
        NoCandidateAvailableError: If state has no candidates.

    Note:
        Outputs the candidate index with the highest mean score across
        all evaluated examples.

    Examples:
        ```python
        policy = FullEvaluationPolicy()
        best_idx = policy.get_best_candidate(state)
        ```
    """
    if not state.candidates:
        raise NoCandidateAvailableError("No candidates available")

    best_idx = None
    best_score = float("-inf")
    for candidate_idx in range(len(state.candidates)):
        score = self.get_valset_score(candidate_idx, state)
        if score > best_score:
            best_score = score
            best_idx = candidate_idx

    if best_idx is None:
        raise NoCandidateAvailableError("No scored candidates available")
    return best_idx

get_valset_score

get_valset_score(
    candidate_idx: int, state: ParetoState
) -> float

Return mean score across all evaluated examples for a candidate.

PARAMETER DESCRIPTION
candidate_idx

Index of candidate to score.

TYPE: int

state

Current evolution state.

TYPE: ParetoState

RETURNS DESCRIPTION
float

Mean score across all examples, or float('-inf') if no scores.

TYPE: float

Note

Outputs the arithmetic mean of all scores for the candidate, or negative infinity if no scores exist.

Examples:

policy = FullEvaluationPolicy()
score = policy.get_valset_score(0, state)
Source code in src/gepa_adk/adapters/selection/evaluation_policy.py
def get_valset_score(self, candidate_idx: int, state: ParetoState) -> float:
    """Return mean score across all evaluated examples for a candidate.

    Args:
        candidate_idx (int): Index of candidate to score.
        state (ParetoState): Current evolution state.

    Returns:
        float: Mean score across all examples, or float('-inf') if no scores.

    Note:
        Outputs the arithmetic mean of all scores for the candidate,
        or negative infinity if no scores exist.

    Examples:
        ```python
        policy = FullEvaluationPolicy()
        score = policy.get_valset_score(0, state)
        ```
    """
    scores = state.candidate_scores.get(candidate_idx)
    if not scores:
        return float("-inf")
    return fmean(scores.values())

SubsetEvaluationPolicy

Evaluation policy that scores a configurable subset with round-robin coverage.

This policy reduces evaluation cost for large validation sets by evaluating only a subset of examples per iteration, using round-robin selection to ensure all examples are eventually covered.

Note

Advances offset each iteration to provide round-robin coverage across the full valset over multiple iterations.

ATTRIBUTE DESCRIPTION
subset_size

If int, absolute count of examples to evaluate per iteration. If float in the range 0.0 to 1.0 exclusive-inclusive, fraction of total valset size.

TYPE: int | float

_offset

Internal state tracking current position for round-robin selection.

TYPE: int

Examples:

# Evaluate 20% of valset per iteration
policy = SubsetEvaluationPolicy(subset_size=0.2)

# Evaluate exactly 5 examples per iteration
policy = SubsetEvaluationPolicy(subset_size=5)
Source code in src/gepa_adk/adapters/selection/evaluation_policy.py
class SubsetEvaluationPolicy:
    """Evaluation policy that scores a configurable subset with round-robin coverage.

    This policy reduces evaluation cost for large validation sets by evaluating
    only a subset of examples per iteration, using round-robin selection to
    ensure all examples are eventually covered.

    Note:
        Advances offset each iteration to provide round-robin coverage across
        the full valset over multiple iterations.

    Attributes:
        subset_size (int | float): If int, absolute count of examples to evaluate
            per iteration. If float in the range 0.0 to 1.0 exclusive-inclusive,
            fraction of total valset size.
        _offset (int): Internal state tracking current position for round-robin
            selection.

    Examples:
        ```python
        # Evaluate 20% of valset per iteration
        policy = SubsetEvaluationPolicy(subset_size=0.2)

        # Evaluate exactly 5 examples per iteration
        policy = SubsetEvaluationPolicy(subset_size=5)
        ```
    """

    def __init__(self, subset_size: int | float = 0.2) -> None:
        """Initialize subset evaluation policy.

        Args:
            subset_size: If int, evaluate this many examples per iteration.
                If float, evaluate this fraction of total valset.
                Default: 0.2 (20% of valset per iteration).

        Note:
            Creates policy with initial offset of 0 for round-robin selection.
        """
        self.subset_size = subset_size
        self._offset = 0

    def get_eval_batch(
        self,
        valset_ids: Sequence[int],
        state: ParetoState,
        target_candidate_idx: int | None = None,
    ) -> list[int]:
        """Return subset of validation example indices with round-robin selection.

        Args:
            valset_ids: All available validation example indices.
            state: Current evolution state (unused for subset selection).
            target_candidate_idx: Optional candidate being evaluated (unused).

        Returns:
            List of example indices to evaluate this iteration.
            Uses round-robin to ensure all examples are eventually covered.

        Raises:
            ValueError: If subset_size is outside the allowed range.

        Note:
            Outputs a subset of valset IDs starting at the current offset,
            wrapping around if needed to provide round-robin coverage.

        Examples:
            ```python
            policy = SubsetEvaluationPolicy(subset_size=0.25)
            batch = policy.get_eval_batch(list(range(8)), state)
            assert len(batch) == 2
            ```
        """
        if not valset_ids:
            return []

        total_size = len(valset_ids)

        # Calculate subset count
        if isinstance(self.subset_size, float):
            if not (0.0 < self.subset_size <= 1.0):
                raise ValueError(
                    f"subset_size float must be in (0.0, 1.0], got {self.subset_size}"
                )
            subset_count = max(1, int(self.subset_size * total_size))
        else:
            subset_count = self.subset_size

        # Fallback to full evaluation if subset_size exceeds valset size
        if subset_count >= total_size:
            return list(valset_ids)

        # Round-robin selection: slice starting at _offset, wrapping around
        result: list[int] = []
        for i in range(subset_count):
            idx = (self._offset + i) % total_size
            result.append(valset_ids[idx])

        # Advance offset for next iteration
        self._offset = (self._offset + subset_count) % total_size

        return result

    def get_best_candidate(self, state: ParetoState) -> int:
        """Return index of candidate with highest average score.

        Args:
            state (ParetoState): Current evolution state with candidate scores.

        Returns:
            int: Index of best performing candidate.

        Raises:
            NoCandidateAvailableError: If state has no candidates.

        Note:
            Outputs the candidate index with the highest mean score across
            evaluated examples, consistent with FullEvaluationPolicy behavior.

        Examples:
            ```python
            policy = SubsetEvaluationPolicy()
            best_idx = policy.get_best_candidate(state)
            ```
        """
        if not state.candidates:
            raise NoCandidateAvailableError("No candidates available")

        best_idx = None
        best_score = float("-inf")
        for candidate_idx in range(len(state.candidates)):
            score = self.get_valset_score(candidate_idx, state)
            if score > best_score:
                best_score = score
                best_idx = candidate_idx

        if best_idx is None:
            raise NoCandidateAvailableError("No scored candidates available")
        return best_idx

    def get_valset_score(self, candidate_idx: int, state: ParetoState) -> float:
        """Return mean score across evaluated examples for a candidate.

        Args:
            candidate_idx (int): Index of candidate to score.
            state (ParetoState): Current evolution state.

        Returns:
            float: Mean score across evaluated examples, or float('-inf') if no scores.

        Note:
            Outputs the arithmetic mean of scores for the candidate across
            only the examples that were actually evaluated (subset).

        Examples:
            ```python
            policy = SubsetEvaluationPolicy()
            score = policy.get_valset_score(0, state)
            ```
        """
        scores = state.candidate_scores.get(candidate_idx)
        if not scores:
            return float("-inf")
        return fmean(scores.values())

__init__

__init__(subset_size: int | float = 0.2) -> None

Initialize subset evaluation policy.

PARAMETER DESCRIPTION
subset_size

If int, evaluate this many examples per iteration. If float, evaluate this fraction of total valset. Default: 0.2 (20% of valset per iteration).

TYPE: int | float DEFAULT: 0.2

Note

Creates policy with initial offset of 0 for round-robin selection.

Source code in src/gepa_adk/adapters/selection/evaluation_policy.py
def __init__(self, subset_size: int | float = 0.2) -> None:
    """Initialize subset evaluation policy.

    Args:
        subset_size: If int, evaluate this many examples per iteration.
            If float, evaluate this fraction of total valset.
            Default: 0.2 (20% of valset per iteration).

    Note:
        Creates policy with initial offset of 0 for round-robin selection.
    """
    self.subset_size = subset_size
    self._offset = 0

get_eval_batch

get_eval_batch(
    valset_ids: Sequence[int],
    state: ParetoState,
    target_candidate_idx: int | None = None,
) -> list[int]

Return subset of validation example indices with round-robin selection.

PARAMETER DESCRIPTION
valset_ids

All available validation example indices.

TYPE: Sequence[int]

state

Current evolution state (unused for subset selection).

TYPE: ParetoState

target_candidate_idx

Optional candidate being evaluated (unused).

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
list[int]

List of example indices to evaluate this iteration.

list[int]

Uses round-robin to ensure all examples are eventually covered.

RAISES DESCRIPTION
ValueError

If subset_size is outside the allowed range.

Note

Outputs a subset of valset IDs starting at the current offset, wrapping around if needed to provide round-robin coverage.

Examples:

policy = SubsetEvaluationPolicy(subset_size=0.25)
batch = policy.get_eval_batch(list(range(8)), state)
assert len(batch) == 2
Source code in src/gepa_adk/adapters/selection/evaluation_policy.py
def get_eval_batch(
    self,
    valset_ids: Sequence[int],
    state: ParetoState,
    target_candidate_idx: int | None = None,
) -> list[int]:
    """Return subset of validation example indices with round-robin selection.

    Args:
        valset_ids: All available validation example indices.
        state: Current evolution state (unused for subset selection).
        target_candidate_idx: Optional candidate being evaluated (unused).

    Returns:
        List of example indices to evaluate this iteration.
        Uses round-robin to ensure all examples are eventually covered.

    Raises:
        ValueError: If subset_size is outside the allowed range.

    Note:
        Outputs a subset of valset IDs starting at the current offset,
        wrapping around if needed to provide round-robin coverage.

    Examples:
        ```python
        policy = SubsetEvaluationPolicy(subset_size=0.25)
        batch = policy.get_eval_batch(list(range(8)), state)
        assert len(batch) == 2
        ```
    """
    if not valset_ids:
        return []

    total_size = len(valset_ids)

    # Calculate subset count
    if isinstance(self.subset_size, float):
        if not (0.0 < self.subset_size <= 1.0):
            raise ValueError(
                f"subset_size float must be in (0.0, 1.0], got {self.subset_size}"
            )
        subset_count = max(1, int(self.subset_size * total_size))
    else:
        subset_count = self.subset_size

    # Fallback to full evaluation if subset_size exceeds valset size
    if subset_count >= total_size:
        return list(valset_ids)

    # Round-robin selection: slice starting at _offset, wrapping around
    result: list[int] = []
    for i in range(subset_count):
        idx = (self._offset + i) % total_size
        result.append(valset_ids[idx])

    # Advance offset for next iteration
    self._offset = (self._offset + subset_count) % total_size

    return result

get_best_candidate

get_best_candidate(state: ParetoState) -> int

Return index of candidate with highest average score.

PARAMETER DESCRIPTION
state

Current evolution state with candidate scores.

TYPE: ParetoState

RETURNS DESCRIPTION
int

Index of best performing candidate.

TYPE: int

RAISES DESCRIPTION
NoCandidateAvailableError

If state has no candidates.

Note

Outputs the candidate index with the highest mean score across evaluated examples, consistent with FullEvaluationPolicy behavior.

Examples:

policy = SubsetEvaluationPolicy()
best_idx = policy.get_best_candidate(state)
Source code in src/gepa_adk/adapters/selection/evaluation_policy.py
def get_best_candidate(self, state: ParetoState) -> int:
    """Return index of candidate with highest average score.

    Args:
        state (ParetoState): Current evolution state with candidate scores.

    Returns:
        int: Index of best performing candidate.

    Raises:
        NoCandidateAvailableError: If state has no candidates.

    Note:
        Outputs the candidate index with the highest mean score across
        evaluated examples, consistent with FullEvaluationPolicy behavior.

    Examples:
        ```python
        policy = SubsetEvaluationPolicy()
        best_idx = policy.get_best_candidate(state)
        ```
    """
    if not state.candidates:
        raise NoCandidateAvailableError("No candidates available")

    best_idx = None
    best_score = float("-inf")
    for candidate_idx in range(len(state.candidates)):
        score = self.get_valset_score(candidate_idx, state)
        if score > best_score:
            best_score = score
            best_idx = candidate_idx

    if best_idx is None:
        raise NoCandidateAvailableError("No scored candidates available")
    return best_idx

get_valset_score

get_valset_score(
    candidate_idx: int, state: ParetoState
) -> float

Return mean score across evaluated examples for a candidate.

PARAMETER DESCRIPTION
candidate_idx

Index of candidate to score.

TYPE: int

state

Current evolution state.

TYPE: ParetoState

RETURNS DESCRIPTION
float

Mean score across evaluated examples, or float('-inf') if no scores.

TYPE: float

Note

Outputs the arithmetic mean of scores for the candidate across only the examples that were actually evaluated (subset).

Examples:

policy = SubsetEvaluationPolicy()
score = policy.get_valset_score(0, state)
Source code in src/gepa_adk/adapters/selection/evaluation_policy.py
def get_valset_score(self, candidate_idx: int, state: ParetoState) -> float:
    """Return mean score across evaluated examples for a candidate.

    Args:
        candidate_idx (int): Index of candidate to score.
        state (ParetoState): Current evolution state.

    Returns:
        float: Mean score across evaluated examples, or float('-inf') if no scores.

    Note:
        Outputs the arithmetic mean of scores for the candidate across
        only the examples that were actually evaluated (subset).

    Examples:
        ```python
        policy = SubsetEvaluationPolicy()
        score = policy.get_valset_score(0, state)
        ```
    """
    scores = state.candidate_scores.get(candidate_idx)
    if not scores:
        return float("-inf")
    return fmean(scores.values())

create_candidate_selector

create_candidate_selector(
    selector_type: str,
    *,
    epsilon: float = 0.1,
    rng: Random | None = None,
) -> CandidateSelectorProtocol

Create a candidate selector by name.

PARAMETER DESCRIPTION
selector_type

Selector identifier (pareto, greedy, epsilon_greedy).

TYPE: str

epsilon

Exploration rate for epsilon-greedy selector.

TYPE: float DEFAULT: 0.1

rng

Optional RNG for selectors using randomness.

TYPE: Random | None DEFAULT: None

RETURNS DESCRIPTION
CandidateSelectorProtocol

CandidateSelectorProtocol implementation.

RAISES DESCRIPTION
ConfigurationError

If selector_type is unsupported.

Examples:

selector = create_candidate_selector("pareto")
candidate_idx = await selector.select_candidate(state)
Source code in src/gepa_adk/adapters/selection/candidate_selector.py
def create_candidate_selector(
    selector_type: str,
    *,
    epsilon: float = 0.1,
    rng: random.Random | None = None,
) -> CandidateSelectorProtocol:
    """Create a candidate selector by name.

    Args:
        selector_type: Selector identifier (pareto, greedy, epsilon_greedy).
        epsilon: Exploration rate for epsilon-greedy selector.
        rng: Optional RNG for selectors using randomness.

    Returns:
        CandidateSelectorProtocol implementation.

    Raises:
        ConfigurationError: If selector_type is unsupported.

    Examples:
        ```python
        selector = create_candidate_selector("pareto")
        candidate_idx = await selector.select_candidate(state)
        ```
    """
    normalized = selector_type.strip().lower()
    if normalized in {"pareto"}:
        return ParetoCandidateSelector(rng=rng)
    if normalized in {"greedy", "current_best", "current-best"}:
        return CurrentBestCandidateSelector()
    if normalized in {"epsilon_greedy", "epsilon-greedy"}:
        return EpsilonGreedyCandidateSelector(epsilon=epsilon, rng=rng)
    raise ConfigurationError(
        "selector_type must be one of pareto, greedy, epsilon_greedy",
        field="selector_type",
        value=selector_type,
        constraint="pareto|greedy|epsilon_greedy",
    )

create_component_selector

create_component_selector(
    selector_type: str,
) -> ComponentSelectorProtocol

Create a component selector strategy from a string alias.

PARAMETER DESCRIPTION
selector_type

Name of the selector strategy. Supported values: - 'round_robin', 'roundrobin': Round-robin cycling. - 'all', 'all_components': All components simultaneously.

TYPE: str

RETURNS DESCRIPTION
ComponentSelectorProtocol

Instance of requested component selector.

RAISES DESCRIPTION
ValueError

If selector_type is unknown.

Examples:

# Create round-robin selector
selector = create_component_selector("round_robin")

# Create all-components selector
selector = create_component_selector("all")
Note

Supports flexible string aliases with normalization for common variations (underscores, hyphens, case-insensitive).

Source code in src/gepa_adk/adapters/selection/component_selector.py
def create_component_selector(selector_type: str) -> ComponentSelectorProtocol:
    """Create a component selector strategy from a string alias.

    Args:
        selector_type: Name of the selector strategy.
            Supported values:
            - 'round_robin', 'roundrobin': Round-robin cycling.
            - 'all', 'all_components': All components simultaneously.

    Returns:
        Instance of requested component selector.

    Raises:
        ValueError: If selector_type is unknown.

    Examples:
        ```python
        # Create round-robin selector
        selector = create_component_selector("round_robin")

        # Create all-components selector
        selector = create_component_selector("all")
        ```

    Note:
        Supports flexible string aliases with normalization for common
        variations (underscores, hyphens, case-insensitive).
    """
    normalized = selector_type.lower().replace("_", "").replace("-", "")

    if normalized == "roundrobin":
        return RoundRobinComponentSelector()
    elif normalized in ("all", "allcomponents"):
        return AllComponentSelector()

    raise ValueError(f"Unknown component selector: {selector_type}")