Source code for chap_core.hpo.base

# from dataclasses import dataclass, field
# from typing import Any, Dict, Optional, List, Protocol, runtime_checkable
# import time
import yaml
import json
from dataclasses import dataclass
from typing import Optional, Any


"""
Constraints on non-categorical hyperparameter values:   
    log=True
        floats: step=None and low/high > 0
        ints: step=1 and low/high > 0
    log=False (uniform)
        floats: step=None (continuous) or step > 0
        ints: step >= 1 and step-type=int
    Bounds are inclusive, when step is used, high is included only if it lies exactly on the grid.
"""


[docs] @dataclass(frozen=True) class Int: low: int high: int step: int = 1 log: bool = False
[docs] @dataclass(frozen=True) class Float: low: float high: float step: Optional[float] = None log: bool = False
# maybe dataclass to pydantic later
[docs] def dedup(values): """Deduplicate while preserving order; works for scalars, lists, dicts, None.""" seen = set() out = [] for v in values if isinstance(values, list) else [values]: try: key = json.dumps(v, sort_keys=True, separators=(",", ":"), default=str) except Exception: key = repr(v) if key not in seen: seen.add(key) out.append(v) return out
[docs] def write_yaml(path: str, data: dict) -> None: with open(path, "w", encoding="utf-8") as f: yaml.safe_dump(data, f, sort_keys=False)
[docs] def load_search_space_from_config(config: dict) -> dict[str, Any]: space: dict[str, Any] = {} for name, spec in config.items(): if not isinstance(spec, dict): raise ValueError(f"'{name}': each spec must be a mapping") # Categorical values if "values" in spec: values = spec["values"] if not isinstance(values, list) or not values: raise ValueError(f"'{name}': 'values' must be a non-empty list") space[name] = values continue if "low" not in spec or "high" not in spec: raise ValueError(f"'{name}': expected 'low' and 'high'") low, high = spec["low"], spec["high"] type_ = spec.get("type", None) # default decided base on type of low, high log = bool(spec.get("log", False)) step = spec.get("step", None) # None for int is 1 # Suggest int if (type_ or "").lower() == "int" or (isinstance(low, int) and isinstance(high, int) and type_ is None): if log and step not in (None, 1): raise ValueError(f"'{name}': log-int requires step==1 (or omit)") step_val = 1 if step is None else int(step) space[name] = Int(low=int(low), high=int(high), step=step_val, log=log) continue # Suggest float if (type_ or "").lower() == "float" or type_ is None: if log and step is not None: raise ValueError(f"'{name}': log-float requires step==None") step_val = None if step is None else float(step) space[name] = Float(low=float(low), high=float(high), step=step_val, log=log) continue raise ValueError(f"'{name}': unknown spec type '{type_}'") print(f"space from load_yaml: {space}") return space
# @dataclass # class ObjectiveResult: # """Standard result returned by an objective evaluation. # Attributes # ---------- # score: float # The scalar to optimize. Higher is better if direction is "maximize". # metrics: Optional[Dict[str, float]] # Optional extra scalar metrics (loss, accuracy, etc.). # info: Optional[Dict[str, Any]] # Free-form info (e.g., training time). Not used by the optimizer. # """ # score: float # metrics: Optional[Dict[str, float]] = None # info: Optional[Dict[str, Any]] = None # @dataclass # class Trial: # id: int # params: Dict[str, Any] # seed: int # started_at: float = field(default_factory=time.time) # ended_at: Optional[float] = None # result: Optional[ObjectiveResult] = None # @dataclass # class Study: # """Container for the full optimization run.""" # trials: List[Trial] # direction: str # "maximize" or "minimize" # best_trial: Optional[Trial] # space: Any # optimizer: "Optimizer" # def best_score(self) -> Optional[float]: # return None if self.best_trial is None else self.best_trial.result.score # type: ignore[union-attr]