Source code for phenotypic.tune._search_space._inferred

"""The ``InferredSearchSpace`` proposal + ``Excluded`` record.

``infer_search_space`` returns a reviewable *proposal*, **not** the
optimizer-facing object. These are frozen pydantic value-models (mirroring
``Knob``/``SearchSpace``) so the proposal round-trips through JSON. Calling
``.to_search_space()`` collapses it to the clean ``SearchSpace`` the strategies
consume — the ``source`` / ``needs_review`` / ``description`` / ``excluded``
data never leaks into the optimizer.
"""
from __future__ import annotations

from typing import Literal

from pydantic import BaseModel, ConfigDict

from ._space import Knob, SearchSpace

#: Why a field was excluded from the inferred space — a closed set (never a bare
#: ``str``). ``"ndarray"``/``"path"``/``"name_ref"``/``"tune_spec_off"`` are
#: *deliberate* exclusions (the field is simply not scalar-tunable, or the author
#: opted it out). ``"non_numeric"`` (a non-numeric / ``None`` anchor for the
#: unbounded heuristic), ``"non_positive_default"`` (a numeric anchor ``<= 0``, so
#: the multiplicative ``[d/f, d·f]`` window collapses or flips sign), and
#: ``"unsupported_type"`` (a multi-type union / unrecognised annotation) are
#: *inference-blind*: inference could not produce a plausible domain, so the
#: proposal flags the whole space for review (see
#: ``InferredSearchSpace.needs_review``).
ExcludeReason = Literal[
    "ndarray",
    "path",
    "name_ref",
    "non_numeric",
    "non_positive_default",
    "tune_spec_off",
    "unsupported_type",
]

#: Exclusion reasons that mean "inference could not even guess" — these raise the
#: proposal-level review flag. The remaining reasons are deliberate exclusions.
_BLIND_REASONS: frozenset[ExcludeReason] = frozenset(
    {"non_numeric", "non_positive_default", "unsupported_type"}
)


[docs] class Excluded(BaseModel): """A field inference could not (or should not) turn into a knob. Args: key: The root-relative path of the excluded field, e.g. ``"0.kernel"``. reason: A closed ``ExcludeReason`` tag explaining the exclusion. field_type: The field's annotation rendered as a string, for display in the "couldn't infer — declare a ``TuneSpec``" review section. """ model_config = ConfigDict(frozen=True, extra="forbid") key: str reason: ExcludeReason field_type: str
[docs] class InferredSearchSpace(BaseModel): """A generous, reviewable proposal of tunable domains. Args: knobs: The tuple of inferred ``Knob`` instances (Tier-1 ``TuneSpec`` overrides + Tier-2 heuristics). excluded: The tuple of ``Excluded`` records for fields that did not become knobs — surfaced so nothing is silently dropped. Note: Frozen value-model: round-trips through JSON so the proposal can be persisted (``--auto-space``) and re-loaded for review. """ model_config = ConfigDict(frozen=True, extra="forbid") knobs: tuple[Knob, ...] excluded: tuple[Excluded, ...] @property def n_knobs(self) -> int: """Number of inferred knobs.""" return len(self.knobs) @property def n_excluded(self) -> int: """Number of excluded fields.""" return len(self.excluded) @property def n_needs_review(self) -> int: """Number of knobs flagged for human review.""" return sum(1 for k in self.knobs if k.needs_review) @property def needs_review(self) -> bool: """Whether the proposal warrants a human check before tuning. ``True`` iff any knob is flagged ``needs_review`` **or** any field was excluded for an inference-blind reason (``non_numeric`` / ``non_positive_default`` / ``unsupported_type``). Deliberate exclusions (ndarray / path / name-ref / ``tune_spec_off``) do not raise the flag. """ if any(k.needs_review for k in self.knobs): return True return any(e.reason in _BLIND_REASONS for e in self.excluded)
[docs] def to_search_space(self) -> SearchSpace: """Collapse to the clean, optimizer-facing ``SearchSpace``. Drops the proposal-only metadata (``source`` / ``needs_review`` / ``description`` / ``excluded``) by keeping only the knobs. Returns: The ``SearchSpace`` the strategies consume. """ return SearchSpace(knobs=self.knobs)