74 lines
2.6 KiB
Python
74 lines
2.6 KiB
Python
"""Declared search space — every tunable parameter in one place (doc 05 §2).
|
|
|
|
A search space is a mapping ``name -> (low, high, step)`` plus a set of
|
|
integer parameter names. The optimizer turns each entry into an Optuna
|
|
``suggest_float`` / ``suggest_int``. Nothing tunable should be hidden in the
|
|
strategy body.
|
|
|
|
Rules (doc 05 §2):
|
|
|
|
- Ranges encode priors, not hope — bracket where the answer plausibly is.
|
|
- Step matters — too fine explodes the space, too coarse misses optima.
|
|
- What is NOT in the space is a decision — list exclusions explicitly.
|
|
- Timeframe is usually fixed per iteration (searching timeframes overfits).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import TypedDict
|
|
|
|
|
|
class Range(TypedDict):
|
|
"""One parameter range: ``low`` to ``high`` in steps of ``step``."""
|
|
|
|
low: float
|
|
high: float
|
|
step: float
|
|
|
|
|
|
# name -> (low, high, step)
|
|
SearchSpace = dict[str, tuple[float, float, float]]
|
|
|
|
|
|
def suggest_params(
|
|
trial,
|
|
space: SearchSpace,
|
|
int_params: set[str] | None = None,
|
|
) -> dict[str, float | int]:
|
|
"""Sample every parameter in ``space`` from an Optuna trial.
|
|
|
|
Integer params (listed in ``int_params``) use ``suggest_int``; floats use
|
|
``suggest_float`` with ``step``. Returns a plain ``dict`` of sampled
|
|
values keyed by parameter name.
|
|
"""
|
|
int_params = int_params or set()
|
|
sampled: dict[str, float | int] = {}
|
|
for name, (low, high, step) in space.items():
|
|
if name in int_params:
|
|
lo = int(round(low))
|
|
hi = int(round(high))
|
|
st = max(int(round(step)), 1)
|
|
sampled[name] = trial.suggest_int(name, lo, hi, step=st)
|
|
else:
|
|
sampled[name] = trial.suggest_float(name, low, high, step=step)
|
|
return sampled
|
|
|
|
|
|
def validate_space(space: SearchSpace, int_params: set[str] | None = None) -> list[str]:
|
|
"""Return a list of problems with the space (empty = OK).
|
|
|
|
Catches: reversed ranges, zero/negative steps, int params with non-integer
|
|
bounds, duplicate names. Run this once before launching a study so a
|
|
malformed space doesn't waste a background run.
|
|
"""
|
|
int_params = int_params or set()
|
|
problems: list[str] = []
|
|
for name, (low, high, step) in space.items():
|
|
if high < low:
|
|
problems.append(f"{name}: high < low ({low} > {high})")
|
|
if step <= 0:
|
|
problems.append(f"{name}: step <= 0 ({step})")
|
|
if name in int_params:
|
|
if int(low) != low or int(high) != high:
|
|
problems.append(f"{name}: int param with non-integer bound ({low}, {high})")
|
|
return problems
|