Skip to content

Commit ca623bc

Browse files
committed
add tests for '_score_params'
1 parent 547bd56 commit ca623bc

2 files changed

Lines changed: 120 additions & 0 deletions

File tree

‎src/hyperactive/opt/tests/__init__.py‎

Whitespace-only changes.
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"""Tests for _score_params to guard against parameter passing regressions."""
2+
3+
import numpy as np
4+
import pytest
5+
6+
from hyperactive.opt._common import _score_params
7+
8+
9+
class _DictExperiment:
10+
"""Minimal experiment stub that expects params as a single dict."""
11+
12+
def __call__(self, params):
13+
return params["x"] ** 2 + params["y"] ** 2
14+
15+
16+
class _DictOnlyExperiment:
17+
"""Experiment stub that rejects keyword arguments.
18+
19+
Fails loudly if params are passed as **kwargs instead of a dict,
20+
directly guarding against the ``experiment(**params)`` bug.
21+
"""
22+
23+
def __call__(self, params):
24+
if not isinstance(params, dict):
25+
raise TypeError(
26+
f"Expected a dict, got {type(params).__name__}. "
27+
"Parameters must be passed as a single dict, not as **kwargs."
28+
)
29+
return sum(v**2 for v in params.values())
30+
31+
32+
def _make_meta(experiment, error_score=np.nan):
33+
return {"experiment": experiment, "error_score": error_score}
34+
35+
36+
class TestScoreParams:
37+
"""Tests for the _score_params helper function."""
38+
39+
def test_params_passed_as_dict(self):
40+
"""Params must be passed as a single dict, not unpacked as **kwargs."""
41+
exp = _DictOnlyExperiment()
42+
meta = _make_meta(exp)
43+
params = {"x": 3.0, "y": 4.0}
44+
45+
score = _score_params(params, meta)
46+
47+
assert score == 25.0
48+
49+
def test_returns_correct_score(self):
50+
"""Score must match the experiment's return value."""
51+
exp = _DictExperiment()
52+
meta = _make_meta(exp)
53+
54+
assert _score_params({"x": 0.0, "y": 0.0}, meta) == 0.0
55+
assert _score_params({"x": 1.0, "y": 0.0}, meta) == 1.0
56+
assert _score_params({"x": 3.0, "y": 4.0}, meta) == 25.0
57+
58+
def test_returns_python_float(self):
59+
"""Return type must be a Python float, not numpy scalar."""
60+
exp = _DictExperiment()
61+
meta = _make_meta(exp)
62+
63+
result = _score_params({"x": 1.0, "y": 1.0}, meta)
64+
assert type(result) is float
65+
66+
def test_error_score_on_exception(self):
67+
"""When the experiment raises, error_score must be returned."""
68+
69+
def _failing_experiment(params):
70+
raise ValueError("intentional failure")
71+
72+
meta = _make_meta(_failing_experiment, error_score=-999.0)
73+
74+
with pytest.warns(match="intentional failure"):
75+
result = _score_params({"x": 1.0}, meta)
76+
77+
assert result == -999.0
78+
79+
def test_error_score_emits_warning(self):
80+
"""A caught exception must produce a warning, never be silent."""
81+
82+
def _failing_experiment(params):
83+
raise RuntimeError("boom")
84+
85+
meta = _make_meta(_failing_experiment, error_score=np.nan)
86+
87+
with pytest.warns(match="RuntimeError"):
88+
_score_params({"x": 1.0}, meta)
89+
90+
def test_many_params_passed_as_dict(self):
91+
"""Regression: many keys must not be unpacked as keyword arguments.
92+
93+
With the old ``experiment(**params)`` bug, this would raise
94+
TypeError inside __call__ because it only accepts one argument.
95+
"""
96+
97+
def _sum_experiment(params):
98+
return sum(params.values())
99+
100+
meta = _make_meta(_sum_experiment)
101+
params = {f"x{i}": float(i) for i in range(20)}
102+
103+
score = _score_params(params, meta)
104+
105+
assert score == float(sum(range(20)))
106+
107+
def test_with_base_experiment(self):
108+
"""Integration: works with a real BaseExperiment subclass."""
109+
from hyperactive.experiment.bench import Sphere
110+
111+
exp = Sphere(n_dim=2)
112+
meta = _make_meta(exp)
113+
114+
# Sphere minimum is at origin, value = 0
115+
# __call__ returns sign-adjusted score (higher is better)
116+
# Sphere is lower-is-better, so score = -evaluate
117+
score_origin = _score_params({"x0": 0.0, "x1": 0.0}, meta)
118+
score_away = _score_params({"x0": 3.0, "x1": 4.0}, meta)
119+
120+
assert score_origin > score_away

0 commit comments

Comments
 (0)