From ff54b4f9e0983ea81e83a4dce49d53caeb2fd74d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Tue, 8 Jul 2025 12:26:56 +0200 Subject: [PATCH 01/23] sign --- extension_templates/experiments.py | 4 + src/hyperactive/base/_experiment.py | 39 ++++++++- .../experiment/integrations/sklearn_cv.py | 84 +++++++++++++++++++ src/hyperactive/opt/_adapters/_gfo.py | 2 +- src/hyperactive/tests/test_all_objects.py | 12 ++- 5 files changed, 137 insertions(+), 4 deletions(-) diff --git a/extension_templates/experiments.py b/extension_templates/experiments.py index e5efe1fe..cc009266 100644 --- a/extension_templates/experiments.py +++ b/extension_templates/experiments.py @@ -77,6 +77,10 @@ class MyExperiment(BaseExperiment): # valid values: "random", "deterministic" # if "deterministic", two calls of score must result in the same value # + "property:higher_or_lower_is_better": "lower", + # valid values: "higher", "lower", "mixed" + # whether higher or lower scores are better + # # -------------- # packaging info # -------------- diff --git a/src/hyperactive/base/_experiment.py b/src/hyperactive/base/_experiment.py index 22036e63..269cf8fc 100644 --- a/src/hyperactive/base/_experiment.py +++ b/src/hyperactive/base/_experiment.py @@ -14,14 +14,16 @@ class BaseExperiment(BaseObject): "property:randomness": "random", # random or deterministic # if deterministic, two calls of score will result in the same value # random = two calls may result in different values; same as "stochastic" + "property:higher_or_lower_is_better": "lower", # "higher", "lower", "mixed" + # whether higher or lower scores are better } def __init__(self): super().__init__() def __call__(self, **kwargs): - """Score parameters, with kwargs call.""" - score, _ = self.score(kwargs) + """Score parameters, with kwargs call. Same as cost call.""" + score, _ = self.cost(kwargs) return score @property @@ -86,3 +88,36 @@ def _score(self, params): Additional metadata about the search. """ raise NotImplementedError + + def cost(self, params): + """Score the parameters - with sign such that lower is better. + + Same as ``score`` call except for the sign. + + If the tag ``property:higher_or_lower_is_better`` is set to + ``"higher"``, the result is ``-self.score(params)``. + + If the tag is set to ``"lower"``, the result is + identical to ``self.score(params)``. + + Parameters + ---------- + params : dict with string keys + Parameters to score. + + Returns + ------- + float + The score of the parameters. + dict + Additional metadata about the search. + """ + hib = self.get_tag("property:higher_or_lower_is_better", "lower") + if hib == "higher": + sign = -1 + elif hib == "lower": + sign = 1 + + score_res = self.score(params) + + return sign * score_res[0], score_res[1] diff --git a/src/hyperactive/experiment/integrations/sklearn_cv.py b/src/hyperactive/experiment/integrations/sklearn_cv.py index bf60db00..788297d4 100644 --- a/src/hyperactive/experiment/integrations/sklearn_cv.py +++ b/src/hyperactive/experiment/integrations/sklearn_cv.py @@ -110,6 +110,13 @@ def __init__(self, estimator, X, y, scoring=None, cv=None): self._scoring = make_scorer(scoring) self.scorer_ = self._scoring + # Set the sign of the scoring function + if hasattr(self._scoring, "_score"): + score_func = self._scoring._score_func + _sign = _guess_sign_of_sklmetric(score_func) + _sign_str = "higher" if _sign == 1 else "lower" + self.set_tags(**{"property:higher_or_lower_is_better": _sign_str}) + def _paramnames(self): """Return the parameter names of the search. @@ -235,3 +242,80 @@ def _get_score_params(self): score_params_regress = {"C": 1.0, "kernel": "linear"} score_params_defaults = {"C": 1.0, "kernel": "linear"} return [score_params_classif, score_params_regress, score_params_defaults] + + +def _guess_sign_of_sklmetric(scorer): + """Guess the sign of a sklearn metric scorer. + + Parameters + ---------- + scorer : callable + The sklearn metric scorer to guess the sign for. + + Returns + ------- + int + 1 if higher scores are better, -1 if lower scores are better. + """ + HIGHER_IS_BETTER = { + # Classification + "accuracy_score": True, + "auc": True, + "average_precision_score": True, + "balanced_accuracy_score": True, + "brier_score_loss": False, + "class_likelihood_ratios": False, + "cohen_kappa_score": True, + "d2_log_loss_score": True, + "dcg_score": True, + "f1_score": True, + "fbeta_score": True, + "hamming_loss": False, + "hinge_loss": False, + "jaccard_score": True, + "log_loss": False, + "matthews_corrcoef": True, + "ndcg_score": True, + "precision_score": True, + "recall_score": True, + "roc_auc_score": True, + "top_k_accuracy_score": True, + "zero_one_loss": False, + + # Regression + "d2_absolute_error_score": True, + "d2_pinball_score": True, + "d2_tweedie_score": True, + "explained_variance_score": True, + "max_error": False, + "mean_absolute_error": False, + "mean_absolute_percentage_error": False, + "mean_gamma_deviance": False, + "mean_pinball_loss": False, + "mean_poisson_deviance": False, + "mean_squared_error": False, + "mean_squared_log_error": False, + "mean_tweedie_deviance": False, + "median_absolute_error": False, + "r2_score": True, + "root_mean_squared_error": False, + "root_mean_squared_log_error": False, + } + + scorer_name = getattr(scorer, "__name__", None) + + if hasattr(scorer, "greater_is_better"): + return 1 if scorer.greater_is_better else -1 + elif scorer_name in HIGHER_IS_BETTER: + return 1 if HIGHER_IS_BETTER[scorer_name] else -1 + elif scorer_name.endswith("_score"): + # If the scorer name ends with "_score", we assume higher is better + return 1 + elif scorer_name.endswith("_loss") or scorer_name.endswith("_deviance"): + # If the scorer name ends with "_loss", we assume lower is better + return -1 + elif scorer_name.endswith("_error"): + return -1 + else: + # If we cannot determine the sign, we assume lower is better + return -1 diff --git a/src/hyperactive/opt/_adapters/_gfo.py b/src/hyperactive/opt/_adapters/_gfo.py index c2bae3d9..8e78a9c3 100644 --- a/src/hyperactive/opt/_adapters/_gfo.py +++ b/src/hyperactive/opt/_adapters/_gfo.py @@ -133,7 +133,7 @@ def _run(self, experiment, **search_config): with StdoutMute(active=not self.verbose): gfopt.search( - objective_function=experiment.score, + objective_function=experiment.cost, n_iter=n_iter, max_time=max_time, ) diff --git a/src/hyperactive/tests/test_all_objects.py b/src/hyperactive/tests/test_all_objects.py index b5b4955f..b503a63d 100644 --- a/src/hyperactive/tests/test_all_objects.py +++ b/src/hyperactive/tests/test_all_objects.py @@ -45,6 +45,7 @@ class PackageConfig: "maintainers", # experiments "property:randomness", + "property:higher_or_lower_is_better", # optimizers "info:name", # str "info:local_vs_global", # "local", "mixed", "global" @@ -184,10 +185,19 @@ def test_score_function(self, object_class): assert isinstance(score, float), f"Score is not a float: {score}" assert isinstance(metadata, dict), f"Metadata is not a dict: {metadata}" + cost_res = inst.cost(obj) + msg = f"Cost function did not return a length two tuple: {res}" + assert isinstance(cost_res, tuple) and len(cost_res) == 2, msg + c_score, c_metadata = cost_res + assert isinstance(c_score, float), f"Score is not a float: {c_score}" + assert isinstance(c_metadata, dict), f"Metadata is not a dict: {c_metadata}" + + assert abs(c_score) == score + call_sc = inst(**obj) assert isinstance(call_sc, float), f"Score is not a float: {call_sc}" if inst.get_tag("property:randomness") == "deterministic": - assert score == call_sc, f"Score does not match: {score} != {call_sc}" + assert c_score == call_sc, f"Score does not match: {score} != {call_sc}" class OptimizerFixtureGenerator(BaseFixtureGenerator): From fa418e42b6c8ac245d42457d18be71e68d91e338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Tue, 8 Jul 2025 12:36:29 +0200 Subject: [PATCH 02/23] Update test_all_objects.py --- src/hyperactive/tests/test_all_objects.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hyperactive/tests/test_all_objects.py b/src/hyperactive/tests/test_all_objects.py index b503a63d..431c9039 100644 --- a/src/hyperactive/tests/test_all_objects.py +++ b/src/hyperactive/tests/test_all_objects.py @@ -197,7 +197,8 @@ def test_score_function(self, object_class): call_sc = inst(**obj) assert isinstance(call_sc, float), f"Score is not a float: {call_sc}" if inst.get_tag("property:randomness") == "deterministic": - assert c_score == call_sc, f"Score does not match: {score} != {call_sc}" + msg = f"Score does not match: {c_score} != {call_sc}" + assert c_score == call_sc, msg class OptimizerFixtureGenerator(BaseFixtureGenerator): From 81ea22ce25be14b05603d693498eb30c530b0874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Tue, 8 Jul 2025 12:38:24 +0200 Subject: [PATCH 03/23] Update test_all_objects.py --- src/hyperactive/tests/test_all_objects.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hyperactive/tests/test_all_objects.py b/src/hyperactive/tests/test_all_objects.py index 431c9039..125ae1d4 100644 --- a/src/hyperactive/tests/test_all_objects.py +++ b/src/hyperactive/tests/test_all_objects.py @@ -192,7 +192,9 @@ def test_score_function(self, object_class): assert isinstance(c_score, float), f"Score is not a float: {c_score}" assert isinstance(c_metadata, dict), f"Metadata is not a dict: {c_metadata}" - assert abs(c_score) == score + if inst.get_tag("property:randomness") == "deterministic": + msg = f"Score and cost calls do not match: |{c_score}| != |{score}|" + assert abs(c_score) == score, msg call_sc = inst(**obj) assert isinstance(call_sc, float), f"Score is not a float: {call_sc}" From 7c023db4881a89adf76505891317b7fb992f9b96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Tue, 8 Jul 2025 12:53:13 +0200 Subject: [PATCH 04/23] Update test_all_objects.py --- src/hyperactive/tests/test_all_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hyperactive/tests/test_all_objects.py b/src/hyperactive/tests/test_all_objects.py index 125ae1d4..900991e5 100644 --- a/src/hyperactive/tests/test_all_objects.py +++ b/src/hyperactive/tests/test_all_objects.py @@ -194,7 +194,7 @@ def test_score_function(self, object_class): if inst.get_tag("property:randomness") == "deterministic": msg = f"Score and cost calls do not match: |{c_score}| != |{score}|" - assert abs(c_score) == score, msg + assert abs(c_score) == abs(score), msg call_sc = inst(**obj) assert isinstance(call_sc, float), f"Score is not a float: {call_sc}" From c161afefbdd7cc902b7ed0916e587fcddba508cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 27 Jul 2025 17:42:17 +0200 Subject: [PATCH 05/23] flip sign --- extension_templates/experiments.py | 14 +++---- src/hyperactive/base/_experiment.py | 38 +++++++++---------- .../experiment/integrations/sklearn_cv.py | 8 ++-- src/hyperactive/experiment/toy/_ackley.py | 19 +++++++++- src/hyperactive/experiment/toy/_parabola.py | 19 +++++++++- src/hyperactive/experiment/toy/_sphere.py | 19 +++++++++- 6 files changed, 84 insertions(+), 33 deletions(-) diff --git a/extension_templates/experiments.py b/extension_templates/experiments.py index cc009266..8c4f0502 100644 --- a/extension_templates/experiments.py +++ b/extension_templates/experiments.py @@ -75,11 +75,11 @@ class MyExperiment(BaseExperiment): # "property:randomness": "random", # valid values: "random", "deterministic" - # if "deterministic", two calls of score must result in the same value + # if "deterministic", two calls of "evaluate" must result in the same value # "property:higher_or_lower_is_better": "lower", # valid values: "higher", "lower", "mixed" - # whether higher or lower scores are better + # whether higher or lower returns of "evaluate" are better # # -------------- # packaging info @@ -151,25 +151,25 @@ def _paramnames(self): return ["score_param1", "score_param2"] # todo: implement this, mandatory - def _score(self, params): - """Score the parameters. + def _evaluate(self, params): + """Evaluate the parameters. Parameters ---------- params : dict with string keys - Parameters to score. + Parameters to evaluate. Returns ------- float - The score of the parameters. + The value of the parameters as per evaluation. dict Additional metadata about the search. """ # params is a dictionary with keys being paramnames or subset thereof # IMPORTANT: avoid side effects to params! # - # the method may work if only a subste of the parameters in paramnames is passed + # the method may work if only a subset of the parameters in paramnames is passed # but this is not necessary value = 42 # must be numpy.float64 metadata = {"some": "metadata"} # can be any dict diff --git a/src/hyperactive/base/_experiment.py b/src/hyperactive/base/_experiment.py index 269cf8fc..c1b870cf 100644 --- a/src/hyperactive/base/_experiment.py +++ b/src/hyperactive/base/_experiment.py @@ -14,7 +14,7 @@ class BaseExperiment(BaseObject): "property:randomness": "random", # random or deterministic # if deterministic, two calls of score will result in the same value # random = two calls may result in different values; same as "stochastic" - "property:higher_or_lower_is_better": "lower", # "higher", "lower", "mixed" + "property:higher_or_lower_is_better": "higher", # "higher", "lower", "mixed" # whether higher or lower scores are better } @@ -22,8 +22,8 @@ def __init__(self): super().__init__() def __call__(self, **kwargs): - """Score parameters, with kwargs call. Same as cost call.""" - score, _ = self.cost(kwargs) + """Score parameters, with kwargs call. Same as score call.""" + score, _ = self.score(kwargs) return score @property @@ -50,55 +50,55 @@ def _paramnames(self): """ raise NotImplementedError - def score(self, params): - """Score the parameters. + def evaluate(self, params): + """Evaluate the parameters. Parameters ---------- params : dict with string keys - Parameters to score. + Parameters to evaluate. Returns ------- float - The score of the parameters. + The value of the parameters as per evaluation. dict Additional metadata about the search. """ paramnames = self.paramnames() if not set(params.keys()) <= set(paramnames): raise ValueError("Parameters do not match.") - res, metadata = self._score(params) + res, metadata = self._evaluate(params) res = np.float64(res) return res, metadata - def _score(self, params): - """Score the parameters. + def _evaluate(self, params): + """Evaluate the parameters. Parameters ---------- params : dict with string keys - Parameters to score. + Parameters to evaluate. Returns ------- float - The score of the parameters. + The value of the parameters as per evaluation. dict Additional metadata about the search. """ raise NotImplementedError - def cost(self, params): - """Score the parameters - with sign such that lower is better. + def score(self, params): + """Score the parameters - with sign such that higher is always better. - Same as ``score`` call except for the sign. + Same as ``evaluate`` call except for the sign chosen so that higher is better. If the tag ``property:higher_or_lower_is_better`` is set to - ``"higher"``, the result is ``-self.score(params)``. + ``"lower"``, the result is ``-self.evaluate(params)``. - If the tag is set to ``"lower"``, the result is - identical to ``self.score(params)``. + If the tag is set to ``"higher"``, the result is + identical to ``self.evaluate(params)``. Parameters ---------- @@ -118,6 +118,6 @@ def cost(self, params): elif hib == "lower": sign = 1 - score_res = self.score(params) + score_res = self.evaluate(params) return sign * score_res[0], score_res[1] diff --git a/src/hyperactive/experiment/integrations/sklearn_cv.py b/src/hyperactive/experiment/integrations/sklearn_cv.py index 788297d4..14650c95 100644 --- a/src/hyperactive/experiment/integrations/sklearn_cv.py +++ b/src/hyperactive/experiment/integrations/sklearn_cv.py @@ -127,18 +127,18 @@ def _paramnames(self): """ return list(self.estimator.get_params().keys()) - def _score(self, params): - """Score the parameters. + def _evaluate(self, params): + """Evaluate the parameters. Parameters ---------- params : dict with string keys - Parameters to score. + Parameters to evaluate. Returns ------- float - The score of the parameters. + The value of the parameters as per evaluation. dict Additional metadata about the search. """ diff --git a/src/hyperactive/experiment/toy/_ackley.py b/src/hyperactive/experiment/toy/_ackley.py index 40d14e15..6981f5f3 100644 --- a/src/hyperactive/experiment/toy/_ackley.py +++ b/src/hyperactive/experiment/toy/_ackley.py @@ -49,6 +49,9 @@ class Ackley(BaseExperiment): "property:randomness": "deterministic", # random or deterministic # if deterministic, two calls of score will result in the same value # random = two calls may result in different values; same as "stochastic" + "property:higher_or_lower_is_better": "lower", + # values are "higher", "lower", "mixed" + # whether higher or lower scores are better } def __init__(self, a=20, b=0.2, c=2 * np.pi, d=2): @@ -61,7 +64,21 @@ def __init__(self, a=20, b=0.2, c=2 * np.pi, d=2): def _paramnames(self): return [f"x{i}" for i in range(self.d)] - def _score(self, params): + def _evaluate(self, params): + """Evaluate the parameters. + + Parameters + ---------- + params : dict with string keys + Parameters to evaluate. + + Returns + ------- + float + The value of the parameters as per evaluation. + dict + Additional metadata about the search. + """ x_vec = np.array([params[f"x{i}"] for i in range(self.d)]) loss1 = -self.a * np.exp(-self.b * np.sqrt(np.sum(x_vec**2) / self.d)) diff --git a/src/hyperactive/experiment/toy/_parabola.py b/src/hyperactive/experiment/toy/_parabola.py index 40893524..b090d8f5 100644 --- a/src/hyperactive/experiment/toy/_parabola.py +++ b/src/hyperactive/experiment/toy/_parabola.py @@ -43,6 +43,9 @@ class Parabola(BaseExperiment): "property:randomness": "deterministic", # random or deterministic # if deterministic, two calls of score will result in the same value # random = two calls may result in different values; same as "stochastic" + "property:higher_or_lower_is_better": "lower", + # values are "higher", "lower", "mixed" + # whether higher or lower scores are better } def __init__(self, a=1.0, b=0.0, c=0.0): @@ -54,7 +57,21 @@ def __init__(self, a=1.0, b=0.0, c=0.0): def _paramnames(self): return ["x", "y"] - def _score(self, params): + def _evaluate(self, params): + """Evaluate the parameters. + + Parameters + ---------- + params : dict with string keys + Parameters to evaluate. + + Returns + ------- + float + The value of the parameters as per evaluation. + dict + Additional metadata about the search. + """ x = params["x"] y = params["y"] diff --git a/src/hyperactive/experiment/toy/_sphere.py b/src/hyperactive/experiment/toy/_sphere.py index afb1493a..7d405278 100644 --- a/src/hyperactive/experiment/toy/_sphere.py +++ b/src/hyperactive/experiment/toy/_sphere.py @@ -50,6 +50,9 @@ class Sphere(BaseExperiment): "property:randomness": "deterministic", # random or deterministic # if deterministic, two calls of score will result in the same value # random = two calls may result in different values; same as "stochastic" + "property:higher_or_lower_is_better": "lower", + # values are "higher", "lower", "mixed" + # whether higher or lower scores are better } def __init__(self, const=0, n_dim=2): @@ -61,7 +64,21 @@ def __init__(self, const=0, n_dim=2): def _paramnames(self): return [f"x{i}" for i in range(self.n_dim)] - def _score(self, params): + def _evaluate(self, params): + """Evaluate the parameters. + + Parameters + ---------- + params : dict with string keys + Parameters to evaluate. + + Returns + ------- + float + The value of the parameters as per evaluation. + dict + Additional metadata about the search. + """ params_vec = np.array([params[f"x{i}"] for i in range(self.n_dim)]) return np.sum(params_vec ** 2) + self.const, {} From 7e99afd23c7c0bd65c7411c85f13bb2c78806227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 27 Jul 2025 17:43:43 +0200 Subject: [PATCH 06/23] tests --- src/hyperactive/opt/_adapters/_gfo.py | 2 +- src/hyperactive/tests/test_all_objects.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/hyperactive/opt/_adapters/_gfo.py b/src/hyperactive/opt/_adapters/_gfo.py index 8e78a9c3..c2bae3d9 100644 --- a/src/hyperactive/opt/_adapters/_gfo.py +++ b/src/hyperactive/opt/_adapters/_gfo.py @@ -133,7 +133,7 @@ def _run(self, experiment, **search_config): with StdoutMute(active=not self.verbose): gfopt.search( - objective_function=experiment.cost, + objective_function=experiment.score, n_iter=n_iter, max_time=max_time, ) diff --git a/src/hyperactive/tests/test_all_objects.py b/src/hyperactive/tests/test_all_objects.py index 900991e5..0cc5612c 100644 --- a/src/hyperactive/tests/test_all_objects.py +++ b/src/hyperactive/tests/test_all_objects.py @@ -185,22 +185,22 @@ def test_score_function(self, object_class): assert isinstance(score, float), f"Score is not a float: {score}" assert isinstance(metadata, dict), f"Metadata is not a dict: {metadata}" - cost_res = inst.cost(obj) - msg = f"Cost function did not return a length two tuple: {res}" - assert isinstance(cost_res, tuple) and len(cost_res) == 2, msg - c_score, c_metadata = cost_res - assert isinstance(c_score, float), f"Score is not a float: {c_score}" - assert isinstance(c_metadata, dict), f"Metadata is not a dict: {c_metadata}" + eval_res = inst.evaluate(obj) + msg = f"eval function did not return a length two tuple: {res}" + assert isinstance(eval_res, tuple) and len(eval_res) == 2, msg + e_score, e_metadata = eval_res + assert isinstance(e_score, float), f"Score is not a float: {e_score}" + assert isinstance(e_metadata, dict), f"Metadata is not a dict: {e_metadata}" if inst.get_tag("property:randomness") == "deterministic": - msg = f"Score and cost calls do not match: |{c_score}| != |{score}|" - assert abs(c_score) == abs(score), msg + msg = f"Score and eval calls do not match: |{e_score}| != |{score}|" + assert abs(e_score) == abs(score), msg call_sc = inst(**obj) assert isinstance(call_sc, float), f"Score is not a float: {call_sc}" if inst.get_tag("property:randomness") == "deterministic": - msg = f"Score does not match: {c_score} != {call_sc}" - assert c_score == call_sc, msg + msg = f"Score does not match: {e_score} != {call_sc}" + assert e_score == call_sc, msg class OptimizerFixtureGenerator(BaseFixtureGenerator): From 02b8440b67bd7a0f9c647a578aca937d6b1b9383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 27 Jul 2025 17:46:17 +0200 Subject: [PATCH 07/23] Update _optimizer.py --- src/hyperactive/base/_optimizer.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/hyperactive/base/_optimizer.py b/src/hyperactive/base/_optimizer.py index 8a3cca54..bf0c8128 100644 --- a/src/hyperactive/base/_optimizer.py +++ b/src/hyperactive/base/_optimizer.py @@ -54,10 +54,21 @@ def get_experiment(self): def run(self): """Run the optimization search process. + The optimization searches for the maximizer of the experiment's + ``score`` method. + + Depending on the tag ``property:higher_or_lower_is_better`` being + set to ``higher`` or ``lower``, the ``run`` method will search for: + + * the minimizer of the ``evaluate`` method if the tag is ``lower`` + * the maximizer of the ``evaluate`` method if the tag is ``higher`` + Returns ------- best_params : dict The best parameters found during the optimization process. + The dict ``best_params`` can be used in ``experiment.score`` or + ``experiment.evaluate`` directly. """ experiment = self.get_experiment() search_config = self.get_search_config() From f959d7c554a85d9bdad4dfd72076dbe7ea786857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 27 Jul 2025 17:52:17 +0200 Subject: [PATCH 08/23] docs --- extension_templates/optimizers.py | 10 ++++++++-- src/hyperactive/base/_optimizer.py | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/extension_templates/optimizers.py b/extension_templates/optimizers.py index 2e62a7c0..4fee0a3d 100644 --- a/extension_templates/optimizers.py +++ b/extension_templates/optimizers.py @@ -142,7 +142,8 @@ def _paramnames(self): return ["score_param1", "score_param2"] # optional: implement this to prepare arguments for _run - # the default is all parameters passed to __init__, except ex + # the default is all parameters passed to __init__, minus the experiment + # the result of this is passed to _run as search_config def get_search_config(self): """Get the search configuration. @@ -153,12 +154,15 @@ def get_search_config(self): """ # the default search_config = super().get_search_config() + # example of adding a new parameter to the search config + # this is optional, but can be useful for clean separation or API interfacing search_config["one_more_param"] = 42 + # this return is available in _run as search_config return search_config # todo: implement this, mandatory def _run(self, experiment, **search_config): - """Run the optimization search process. + """Run the optimization search process to maximize the experiment's score. Parameters ---------- @@ -173,6 +177,8 @@ def _run(self, experiment, **search_config): The best parameters found during the search. Must have keys a subset or identical to experiment.paramnames(). """ + # important: the search logic should *maximize* the experiment's score + # this is the main method to implement, it should return the best parameters best_params = {"write_some_logic_to_get": "best_params"} return best_params diff --git a/src/hyperactive/base/_optimizer.py b/src/hyperactive/base/_optimizer.py index bf0c8128..dba7eea1 100644 --- a/src/hyperactive/base/_optimizer.py +++ b/src/hyperactive/base/_optimizer.py @@ -52,9 +52,9 @@ def get_experiment(self): return self._experiment def run(self): - """Run the optimization search process. + """Run the optimization search process to maximize the experiment's score. - The optimization searches for the maximizer of the experiment's + The optimization searches for a maximizer of the experiment's ``score`` method. Depending on the tag ``property:higher_or_lower_is_better`` being From fc29310f934b0c193cd3e7cbc8722f5a0be82fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 27 Jul 2025 18:01:51 +0200 Subject: [PATCH 09/23] fix error of sign --- src/hyperactive/base/_experiment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hyperactive/base/_experiment.py b/src/hyperactive/base/_experiment.py index c1b870cf..fb85bc04 100644 --- a/src/hyperactive/base/_experiment.py +++ b/src/hyperactive/base/_experiment.py @@ -114,9 +114,9 @@ def score(self, params): """ hib = self.get_tag("property:higher_or_lower_is_better", "lower") if hib == "higher": - sign = -1 - elif hib == "lower": sign = 1 + elif hib == "lower": + sign = -1 score_res = self.evaluate(params) From cdf0db3df6f6aeab2bc16aa303148da937eb9a2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 27 Jul 2025 18:02:50 +0200 Subject: [PATCH 10/23] Update _experiment.py --- src/hyperactive/base/_experiment.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/hyperactive/base/_experiment.py b/src/hyperactive/base/_experiment.py index fb85bc04..db5d2aeb 100644 --- a/src/hyperactive/base/_experiment.py +++ b/src/hyperactive/base/_experiment.py @@ -118,6 +118,8 @@ def score(self, params): elif hib == "lower": sign = -1 - score_res = self.evaluate(params) + eval_res = self.evaluate(params) + value = eval_res[0] + metadata = eval_res[1] - return sign * score_res[0], score_res[1] + return sign * value, metadata From ef64b58d97d45f65810200b2ac8ad9f8c577cf46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 27 Jul 2025 18:09:01 +0200 Subject: [PATCH 11/23] Update test_all_objects.py --- src/hyperactive/tests/test_all_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hyperactive/tests/test_all_objects.py b/src/hyperactive/tests/test_all_objects.py index 0cc5612c..c0842f66 100644 --- a/src/hyperactive/tests/test_all_objects.py +++ b/src/hyperactive/tests/test_all_objects.py @@ -199,7 +199,7 @@ def test_score_function(self, object_class): call_sc = inst(**obj) assert isinstance(call_sc, float), f"Score is not a float: {call_sc}" if inst.get_tag("property:randomness") == "deterministic": - msg = f"Score does not match: {e_score} != {call_sc}" + msg = f"Score does not match: {res} != {call_sc}" assert e_score == call_sc, msg From 2ca896859f97af990a81899e5d03e2d94cf85690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 27 Jul 2025 18:10:51 +0200 Subject: [PATCH 12/23] Update test_all_objects.py --- src/hyperactive/tests/test_all_objects.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/hyperactive/tests/test_all_objects.py b/src/hyperactive/tests/test_all_objects.py index c0842f66..f452bc0a 100644 --- a/src/hyperactive/tests/test_all_objects.py +++ b/src/hyperactive/tests/test_all_objects.py @@ -192,16 +192,24 @@ def test_score_function(self, object_class): assert isinstance(e_score, float), f"Score is not a float: {e_score}" assert isinstance(e_metadata, dict), f"Metadata is not a dict: {e_metadata}" - if inst.get_tag("property:randomness") == "deterministic": + det_tag = inst.get_tag("property:randomness", "random") + + if det_tag == "deterministic": msg = f"Score and eval calls do not match: |{e_score}| != |{score}|" assert abs(e_score) == abs(score), msg call_sc = inst(**obj) assert isinstance(call_sc, float), f"Score is not a float: {call_sc}" - if inst.get_tag("property:randomness") == "deterministic": + if det_tag == "deterministic": msg = f"Score does not match: {res} != {call_sc}" assert e_score == call_sc, msg + sign_tag = inst.get_tag("property:higher_or_lower_is_better", "lower") + if sign_tag == "higher" and det_tag == "deterministic": + assert e_score == res + elif sign_tag == "lower" and det_tag == "deterministic": + assert e_score == -res + class OptimizerFixtureGenerator(BaseFixtureGenerator): """Fixture generator for optimizers. From 3488f808b65f46b8abac36bef61d5c43aae39194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 27 Jul 2025 18:11:02 +0200 Subject: [PATCH 13/23] Update test_all_objects.py --- src/hyperactive/tests/test_all_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hyperactive/tests/test_all_objects.py b/src/hyperactive/tests/test_all_objects.py index f452bc0a..61ff9603 100644 --- a/src/hyperactive/tests/test_all_objects.py +++ b/src/hyperactive/tests/test_all_objects.py @@ -204,7 +204,7 @@ def test_score_function(self, object_class): msg = f"Score does not match: {res} != {call_sc}" assert e_score == call_sc, msg - sign_tag = inst.get_tag("property:higher_or_lower_is_better", "lower") + sign_tag = inst.get_tag("property:higher_or_lower_is_better", "higher") if sign_tag == "higher" and det_tag == "deterministic": assert e_score == res elif sign_tag == "lower" and det_tag == "deterministic": From 851af4a62e4ec9108e641cb5f01c88091590953f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 27 Jul 2025 18:23:25 +0200 Subject: [PATCH 14/23] Update test_all_objects.py --- src/hyperactive/tests/test_all_objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hyperactive/tests/test_all_objects.py b/src/hyperactive/tests/test_all_objects.py index 61ff9603..126b070c 100644 --- a/src/hyperactive/tests/test_all_objects.py +++ b/src/hyperactive/tests/test_all_objects.py @@ -201,14 +201,14 @@ def test_score_function(self, object_class): call_sc = inst(**obj) assert isinstance(call_sc, float), f"Score is not a float: {call_sc}" if det_tag == "deterministic": - msg = f"Score does not match: {res} != {call_sc}" + msg = f"Score does not match: {e_score} != {call_sc}" assert e_score == call_sc, msg sign_tag = inst.get_tag("property:higher_or_lower_is_better", "higher") if sign_tag == "higher" and det_tag == "deterministic": - assert e_score == res + assert score == e_score elif sign_tag == "lower" and det_tag == "deterministic": - assert e_score == -res + assert score == -e_score class OptimizerFixtureGenerator(BaseFixtureGenerator): From 60a6d81375d954a7f4a4d8d59dcfea2b26e2a5e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 27 Jul 2025 18:42:11 +0200 Subject: [PATCH 15/23] negative --- src/hyperactive/base/_experiment.py | 12 ++ .../experiment/compose/__init__.py | 7 + .../experiment/compose/_negative.py | 130 ++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 src/hyperactive/experiment/compose/__init__.py create mode 100644 src/hyperactive/experiment/compose/_negative.py diff --git a/src/hyperactive/base/_experiment.py b/src/hyperactive/base/_experiment.py index db5d2aeb..46491587 100644 --- a/src/hyperactive/base/_experiment.py +++ b/src/hyperactive/base/_experiment.py @@ -30,6 +30,18 @@ def __call__(self, **kwargs): def __name__(self): return type(self).__name__ + def __neg__(self): + """Negate the experiment. + + Returns + ------- + BaseExperiment + A new experiment with negated score. + """ + from hyperactive.experiment.compose import Negative + # return a new experiment with negated score + return Negative(self) + def paramnames(self): """Return the parameter names of the search. diff --git a/src/hyperactive/experiment/compose/__init__.py b/src/hyperactive/experiment/compose/__init__.py new file mode 100644 index 00000000..6adbd893 --- /dev/null +++ b/src/hyperactive/experiment/compose/__init__.py @@ -0,0 +1,7 @@ +"""Composition classes for experiments.""" +# copyright: hyperactive developers, MIT License (see LICENSE file) + + +from hyperactive.experiment.compose._negative import Negative + +__all__ = ["Negative"] diff --git a/src/hyperactive/experiment/compose/_negative.py b/src/hyperactive/experiment/compose/_negative.py new file mode 100644 index 00000000..fe01e0cc --- /dev/null +++ b/src/hyperactive/experiment/compose/_negative.py @@ -0,0 +1,130 @@ +"""Negation of experiment.""" +# copyright: hyperactive developers, MIT License (see LICENSE file) + +from hyperactive.base import BaseExperiment + + +class Negative(BaseExperiment): + """Negative of an experiment - flips the sign of the score. + + Can also be invoked by using the unary negation operator ``-`` + on an experiment instance, e.g., ``-experiment``. + + Useful in baselines or composite objectives. + + This composition class is configurable and allows to negate separately: + + * the sign of the score returned by the experiment, + * the orientation of the optimization (minimization vs maximization). + + By default, both the score and the orientation are flipped, + i.e., an experiment to maximize a function ``f(x)`` becomes an + experiment to minimize ``-f(x)``, and vice versa. + + Parameters + ---------- + experiment : BaseExperiment + The experiment to be negated. It should be an instance of ``BaseExperiment``. + flip_score : bool, default=True + Whether to flip the score of the experiment. If True, the score will be + negated, i.e., the score will be ``-f`` where ``f`` is the original score. + flip_orientation : bool, default=True + Whether to flip the orientation of the optimization. If True, + minimization and maximization will be swapped in the experiment. + + Example + ------- + >>> import numpy as np + >>> from hyperactive.toy.ackley import Ackley + >>> from hyperactive.experiment.compose import Negative + >>> + >>> ackley_exp = Ackley(a=20, b=0.2, c=2 * np.pi, d=2) + >>> neg_ackley_exp = Negative(ackley_exp) + """ + + def __init__(self, experiment, flip_score=True, flip_orientation=True): + self.experiment = experiment + self.flip_score = flip_score + self.flip_orientation = flip_orientation + + super().__init__() + + if self.flip_orientation: + current_tag = self.get_tag("property:higher_or_lower_is_better", "mixed") + if current_tag == "higher": + self.set_tags(**{"property:higher_or_lower_is_better": "lower"}) + elif current_tag == "lower": + self.set_tags(**{"property:higher_or_lower_is_better": "higher"}) + + def _paramnames(self): + """Return the parameter names of the search. + + Returns + ------- + list of str + The parameter names of the search parameters. + """ + return self.experiment.paramnames() + + def _evaluate(self, params): + """Evaluate the parameters. + + Parameters + ---------- + params : dict with string keys + Parameters to evaluate. + + Returns + ------- + float + The value of the parameters as per evaluation. + dict + Additional metadata about the search. + """ + value, metadata = self.experiment.evaluate(params) + if self.flip_score: + value = -value + return value, metadata + + @classmethod + def get_test_params(cls, parameter_set="default"): + """Return testing parameter settings for the skbase object. + + ``get_test_params`` is a unified interface point to store + parameter settings for testing purposes. This function is also + used in ``create_test_instance`` and ``create_test_instances_and_names`` + to construct test instances. + + ``get_test_params`` should return a single ``dict``, or a ``list`` of ``dict``. + + Each ``dict`` is a parameter configuration for testing, + and can be used to construct an "interesting" test instance. + A call to ``cls(**params)`` should + be valid for all dictionaries ``params`` in the return of ``get_test_params``. + + The ``get_test_params`` need not return fixed lists of dictionaries, + it can also return dynamic or stochastic parameter settings. + + Parameters + ---------- + parameter_set : str, default="default" + Name of the set of test parameters to return, for use in tests. If no + special parameters are defined for a value, will return `"default"` set. + + Returns + ------- + params : dict or list of dict, default = {} + Parameters to create testing instances of the class + Each dict are parameters to construct an "interesting" test instance, i.e., + `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance. + `create_test_instance` uses the first (or only) dictionary in `params` + """ + from hyperactive.toy.ackley import Ackley + + ackley_exp = Ackley(a=20, b=0.2, c=2, d=2) + + params0 = {"experiment": ackley_exp} + params1 = {"experiment": ackley_exp, "flip_orientation": False} + params2 = {"experiment": ackley_exp, "flip_score": False} + + return [params0, params1, params2] From 3f6c8c8c2242fc72117625bc24b5ed32afb83a80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 27 Jul 2025 18:42:21 +0200 Subject: [PATCH 16/23] Update _negative.py --- src/hyperactive/experiment/compose/_negative.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hyperactive/experiment/compose/_negative.py b/src/hyperactive/experiment/compose/_negative.py index fe01e0cc..762c7c62 100644 --- a/src/hyperactive/experiment/compose/_negative.py +++ b/src/hyperactive/experiment/compose/_negative.py @@ -25,9 +25,11 @@ class Negative(BaseExperiment): ---------- experiment : BaseExperiment The experiment to be negated. It should be an instance of ``BaseExperiment``. + flip_score : bool, default=True Whether to flip the score of the experiment. If True, the score will be negated, i.e., the score will be ``-f`` where ``f`` is the original score. + flip_orientation : bool, default=True Whether to flip the orientation of the optimization. If True, minimization and maximization will be swapped in the experiment. From 18c7faace0ec44c6043dd1938eaf79724823ebac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 27 Jul 2025 18:45:11 +0200 Subject: [PATCH 17/23] Update test_all_objects.py --- src/hyperactive/tests/test_all_objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hyperactive/tests/test_all_objects.py b/src/hyperactive/tests/test_all_objects.py index 126b070c..3635b3bb 100644 --- a/src/hyperactive/tests/test_all_objects.py +++ b/src/hyperactive/tests/test_all_objects.py @@ -201,8 +201,8 @@ def test_score_function(self, object_class): call_sc = inst(**obj) assert isinstance(call_sc, float), f"Score is not a float: {call_sc}" if det_tag == "deterministic": - msg = f"Score does not match: {e_score} != {call_sc}" - assert e_score == call_sc, msg + msg = f"Score does not match: {score} != {call_sc}" + assert score == call_sc, msg sign_tag = inst.get_tag("property:higher_or_lower_is_better", "higher") if sign_tag == "higher" and det_tag == "deterministic": From 33a619ed51cc238f4a1b600fc4d92a4d7e1df551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 27 Jul 2025 19:03:47 +0200 Subject: [PATCH 18/23] Update _negative.py --- src/hyperactive/experiment/compose/_negative.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hyperactive/experiment/compose/_negative.py b/src/hyperactive/experiment/compose/_negative.py index 762c7c62..2614a0de 100644 --- a/src/hyperactive/experiment/compose/_negative.py +++ b/src/hyperactive/experiment/compose/_negative.py @@ -121,7 +121,7 @@ def get_test_params(cls, parameter_set="default"): `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance. `create_test_instance` uses the first (or only) dictionary in `params` """ - from hyperactive.toy.ackley import Ackley + from hyperactive.experiment.toy import Ackley ackley_exp = Ackley(a=20, b=0.2, c=2, d=2) From 2433a853d70d2f510fcab48b73d83d5ed14470e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 27 Jul 2025 19:36:22 +0200 Subject: [PATCH 19/23] Update experiments.py --- extension_templates/experiments.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension_templates/experiments.py b/extension_templates/experiments.py index 8c4f0502..efed25fe 100644 --- a/extension_templates/experiments.py +++ b/extension_templates/experiments.py @@ -245,7 +245,7 @@ def _get_score_params(self): The parameters to be used for scoring. """ # dict keys should be same as paramnames return - # or subset, only if _score allows for subsets of parameters + # or subset, only if _evaluate allows for subsets of parameters score_params1 = {"score_param1": "foo", "score_param2": "bar"} score_params2 = {"score_param1": "baz", "score_param2": "qux"} return [score_params1, score_params2] From 247f7473c6b1a3aa9b317b4ea1aa33df802e230d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 27 Jul 2025 19:36:44 +0200 Subject: [PATCH 20/23] Update experiments.py --- extension_templates/experiments.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension_templates/experiments.py b/extension_templates/experiments.py index efed25fe..aaa48b32 100644 --- a/extension_templates/experiments.py +++ b/extension_templates/experiments.py @@ -237,7 +237,7 @@ def _get_score_params(self): """Return settings for testing the score function. Used in tests only. Returns a list, the i-th element corresponds to self.get_test_params()[i]. - It should be a valid call for self.score. + It should be a valid call for self.evaluate. Returns ------- From 8a2a14ecc078d41eb5317891046b90dceb8d8a53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 27 Jul 2025 19:38:56 +0200 Subject: [PATCH 21/23] docstr --- extension_templates/experiments.py | 7 ++++--- src/hyperactive/experiment/integrations/sklearn_cv.py | 7 ++++--- src/hyperactive/experiment/toy/_ackley.py | 7 ++++--- src/hyperactive/experiment/toy/_parabola.py | 7 ++++--- src/hyperactive/experiment/toy/_sphere.py | 7 ++++--- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/extension_templates/experiments.py b/extension_templates/experiments.py index aaa48b32..d3a5496a 100644 --- a/extension_templates/experiments.py +++ b/extension_templates/experiments.py @@ -234,10 +234,11 @@ def get_test_params(cls, parameter_set="default"): @classmethod def _get_score_params(self): - """Return settings for testing the score function. Used in tests only. + """Return settings for testing score/evaluate functions. Used in tests only. - Returns a list, the i-th element corresponds to self.get_test_params()[i]. - It should be a valid call for self.evaluate. + Returns a list, the i-th element should be valid arguments for + self.evaluate and self.score, of an instance constructed with + self.get_test_params()[i]. Returns ------- diff --git a/src/hyperactive/experiment/integrations/sklearn_cv.py b/src/hyperactive/experiment/integrations/sklearn_cv.py index 14650c95..fa32f7fe 100644 --- a/src/hyperactive/experiment/integrations/sklearn_cv.py +++ b/src/hyperactive/experiment/integrations/sklearn_cv.py @@ -228,10 +228,11 @@ def get_test_params(cls, parameter_set="default"): @classmethod def _get_score_params(self): - """Return settings for testing the score function. Used in tests only. + """Return settings for testing score/evaluate functions. Used in tests only. - Returns a list, the i-th element corresponds to self.get_test_params()[i]. - It should be a valid call for self.score. + Returns a list, the i-th element should be valid arguments for + self.evaluate and self.score, of an instance constructed with + self.get_test_params()[i]. Returns ------- diff --git a/src/hyperactive/experiment/toy/_ackley.py b/src/hyperactive/experiment/toy/_ackley.py index 6981f5f3..6eb67733 100644 --- a/src/hyperactive/experiment/toy/_ackley.py +++ b/src/hyperactive/experiment/toy/_ackley.py @@ -127,10 +127,11 @@ def get_test_params(cls, parameter_set="default"): @classmethod def _get_score_params(self): - """Return settings for testing the score function. Used in tests only. + """Return settings for testing score/evaluate functions. Used in tests only. - Returns a list, the i-th element corresponds to self.get_test_params()[i]. - It should be a valid call for self.score. + Returns a list, the i-th element should be valid arguments for + self.evaluate and self.score, of an instance constructed with + self.get_test_params()[i]. Returns ------- diff --git a/src/hyperactive/experiment/toy/_parabola.py b/src/hyperactive/experiment/toy/_parabola.py index b090d8f5..d9672f96 100644 --- a/src/hyperactive/experiment/toy/_parabola.py +++ b/src/hyperactive/experiment/toy/_parabola.py @@ -79,10 +79,11 @@ def _evaluate(self, params): @classmethod def _get_score_params(self): - """Return settings for testing the score function. Used in tests only. + """Return settings for testing score/evaluate functions. Used in tests only. - Returns a list, the i-th element corresponds to self.get_test_params()[i]. - It should be a valid call for self.score. + Returns a list, the i-th element should be valid arguments for + self.evaluate and self.score, of an instance constructed with + self.get_test_params()[i]. Returns ------- diff --git a/src/hyperactive/experiment/toy/_sphere.py b/src/hyperactive/experiment/toy/_sphere.py index 7d405278..09e9e5c6 100644 --- a/src/hyperactive/experiment/toy/_sphere.py +++ b/src/hyperactive/experiment/toy/_sphere.py @@ -121,10 +121,11 @@ def get_test_params(cls, parameter_set="default"): @classmethod def _get_score_params(self): - """Return settings for testing the score function. Used in tests only. + """Return settings for testing score/evaluate functions. Used in tests only. - Returns a list, the i-th element corresponds to self.get_test_params()[i]. - It should be a valid call for self.score. + Returns a list, the i-th element should be valid arguments for + self.evaluate and self.score, of an instance constructed with + self.get_test_params()[i]. Returns ------- From a9f8a8f0d94c13795a818098e0558e4798d40914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 27 Jul 2025 19:39:48 +0200 Subject: [PATCH 22/23] Update _negative.py --- src/hyperactive/experiment/compose/_negative.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/hyperactive/experiment/compose/_negative.py b/src/hyperactive/experiment/compose/_negative.py index 2614a0de..98e881e4 100644 --- a/src/hyperactive/experiment/compose/_negative.py +++ b/src/hyperactive/experiment/compose/_negative.py @@ -130,3 +130,19 @@ def get_test_params(cls, parameter_set="default"): params2 = {"experiment": ackley_exp, "flip_score": False} return [params0, params1, params2] + + @classmethod + def _get_score_params(self): + """Return settings for testing score/evaluate functions. Used in tests only. + + Returns a list, the i-th element should be valid arguments for + self.evaluate and self.score, of an instance constructed with + self.get_test_params()[i]. + + Returns + ------- + list of dict + The parameters to be used for scoring. + """ + params = {"x0": 0, "x1": 0} + return [params, params, params] From d78f748264b83cba9ea17fe99a7258567fa659f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Sun, 27 Jul 2025 19:54:44 +0200 Subject: [PATCH 23/23] Update _negative.py --- src/hyperactive/experiment/compose/_negative.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hyperactive/experiment/compose/_negative.py b/src/hyperactive/experiment/compose/_negative.py index 98e881e4..bfb6d557 100644 --- a/src/hyperactive/experiment/compose/_negative.py +++ b/src/hyperactive/experiment/compose/_negative.py @@ -37,7 +37,7 @@ class Negative(BaseExperiment): Example ------- >>> import numpy as np - >>> from hyperactive.toy.ackley import Ackley + >>> from hyperactive.experiment.toy import Ackley >>> from hyperactive.experiment.compose import Negative >>> >>> ackley_exp = Ackley(a=20, b=0.2, c=2 * np.pi, d=2)