diff --git a/src/spotforecast2/model_selection/spotoptim_search.py b/src/spotforecast2/model_selection/spotoptim_search.py index fdf34c5..8ed62b9 100644 --- a/src/spotforecast2/model_selection/spotoptim_search.py +++ b/src/spotforecast2/model_selection/spotoptim_search.py @@ -611,7 +611,12 @@ def spotoptim_search( config_counter = None config_counter_lock = None counter_manager = None - if show_progress and parallel_eval: + # NOT gated on ``show_progress``: the per-config fold bars are always on + # (``_objective_wrapper`` passes ``show_progress=True`` to the objective + # below), so the label needs the shared counter in every parallel run — + # the MultiTask pipeline reaches this with ``show_progress=False`` and + # would otherwise show frozen "config 1/N" labels again. + if parallel_eval: counter_manager = multiprocessing.Manager() config_counter = counter_manager.Value("i", 0) config_counter_lock = counter_manager.Lock() diff --git a/tests/test_spotoptim_search.py b/tests/test_spotoptim_search.py index f12feec..e95604b 100644 --- a/tests/test_spotoptim_search.py +++ b/tests/test_spotoptim_search.py @@ -476,6 +476,35 @@ class FakeCounter: assert descs == ["config 6/10", "config 7/10"] assert counter.value == 7 + def test_parallel_labels_increment_with_show_progress_false( + self, y_series, forecaster, cv, capfd + ): + """Labels must increment even when the OUTER show_progress is False. + + The MultiTask pipeline calls spotoptim_search with + show_progress=False, yet the per-config fold bars are always shown + (the objective wrapper hardcodes show_progress=True). Gating the + shared counter on the outer flag therefore reintroduced frozen + 'config 1/N' labels in every real pipeline run — this is the exact + scenario from the 2026-06-07 team4_submit report. + """ + spotoptim_search( + forecaster=forecaster, + y=y_series, + cv=cv, + search_space={"alpha": (0.01, 10.0)}, + metric="mean_absolute_error", + n_trials=4, + n_initial=2, + return_best=False, + verbose=False, + show_progress=False, + kwargs_spotoptim={"n_jobs": 2}, + ) + err = capfd.readouterr().err + for k in range(1, 5): + assert f"config {k}/4" in err, f"missing 'config {k}/4' in:\n{err}" + def test_parallel_search_labels_increment(self, y_series, forecaster, cv, capfd): """End to end: SpotOptim n_jobs=2 must not repeat 'config 1/N'. diff --git a/uv.lock b/uv.lock index 7dd6068..1fde484 100644 --- a/uv.lock +++ b/uv.lock @@ -3604,7 +3604,7 @@ wheels = [ [[package]] name = "spotforecast2" -version = "5.0.0" +version = "5.1.0" source = { editable = "." } dependencies = [ { name = "astral" },