Source code for phenotypic.tune.strategy._random
"""RandomStrategy — seeded random sampling over the SearchSpace."""
from __future__ import annotations
import math
import random
from collections.abc import Mapping
from typing import Any
from .._search_space import (
Categorical,
Domain,
Fixed,
FloatRange,
IntRange,
SearchSpace,
)
from ._pruning import NoOpChannel, PruningChannel
[docs]
class RandomStrategy:
"""Samples ``n_trials`` random configurations under a fixed seed.
Respects ``conditional_on``: a child knob is sampled only when its parent
presence value was sampled to match. Relies on a parent presence knob
appearing **before** its conditional children in ``space.knobs`` (true for
inferred and hand-authored spaces — the ``__enabled__`` knob is emitted
first); a topological pass is unnecessary at the depth-cap of 1.
Args:
space: The search space to sample over.
n_trials: The number of configurations to draw before exhaustion.
seed: The RNG seed; fixing it makes the sampled sequence deterministic.
"""
def __init__(self, space: SearchSpace, *, n_trials: int, seed: int = 0) -> None:
self._space = space
self._n = n_trials
self._rng = random.Random(seed)
self._count = 0
def _sample_domain(self, domain: Domain) -> Any:
if isinstance(domain, Categorical):
return self._rng.choice(list(domain.choices))
if isinstance(domain, IntRange):
return self._rng.choice(domain.values())
if isinstance(domain, FloatRange):
if domain.step is not None:
return self._rng.choice(domain.values())
if domain.log:
lo, hi = math.log(domain.low), math.log(domain.high)
return math.exp(self._rng.uniform(lo, hi))
return self._rng.uniform(domain.low, domain.high)
if isinstance(domain, Fixed):
return domain.value
raise TypeError(f"unsupported domain {type(domain).__name__}")
[docs]
def suggest(self) -> tuple[Mapping[str, Any], PruningChannel]:
chosen: dict[str, Any] = {}
# Sample knobs in order; a conditional knob is sampled only if active.
for knob in self._space.knobs:
if not knob.is_active(chosen):
continue
chosen[knob.key] = self._sample_domain(knob.domain)
self._count += 1
return chosen, NoOpChannel()
[docs]
def register_result(
self, params: Mapping[str, Any], result: Any, *, pruned: bool = False
) -> None:
return None # random does not learn
[docs]
def is_exhausted(self) -> bool:
return self._count >= self._n