diff --git a/CHANGELOG.md b/CHANGELOG.md index e2ff8ea7..ff7b8d93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## [8.0.0-rc.1](https://github.com/sequential-parameter-optimization/spotforecast2/compare/v7.1.0...v8.0.0-rc.1) (2026-06-10) + +### ⚠ BREAKING CHANGES + +* **multitask:** configs with warm_start_lags=True no longer warm-start +the search with lags_consider; set warm_start_lags to the seed lag list +itself (or None to disable). + +Co-Authored-By: Claude Fable 5 + +### Features + +* **multitask:** seed SpotOptim warm start from warm_start_lags list ([8565b8f](https://github.com/sequential-parameter-optimization/spotforecast2/commit/8565b8fec2273873e006496c4d0fefd690a1bea2)) + ## [7.1.0](https://github.com/sequential-parameter-optimization/spotforecast2/compare/v7.0.0...v7.1.0) (2026-06-10) ### Features diff --git a/pyproject.toml b/pyproject.toml index 9e9c6edb..8264d3c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "spotforecast2" -version = "7.1.0" +version = "8.0.0-rc.1" description = "Forecasting with spot" readme = "README.md" license = { text = "AGPL-3.0-or-later" } @@ -31,9 +31,10 @@ dependencies = [ "ruff>=0.15.6", "scikit-learn>=1.8.0", "shap>=0.49.1", - # 21.2.0 added the max_time_spotoptim config field that SpotOptimStrategy - # forwards as SpotOptim's max_time. - "spotforecast2-safe>=21.2.0,<22", + # 22.0.0 made warm_start_lags the seed lag list itself (default + # DEFAULT_WARM_START_LAGS, None disables) — consumed by SpotOptimStrategy. + # 21.2.0 added max_time_spotoptim, forwarded as SpotOptim's max_time. + "spotforecast2-safe>=22.0.0,<23", # spotoptim 1.0 is sequential-only and lean: torch/tensorboard moved to its # ``[torch]`` extra. sf2 forwards tensorboard_* kwargs into SpotOptim, so we # pin the extra to keep the TensorBoard tuning dashboards working (they were diff --git a/src/spotforecast2/model_selection/spotoptim_search.py b/src/spotforecast2/model_selection/spotoptim_search.py index 9f462386..8f905295 100644 --- a/src/spotforecast2/model_selection/spotoptim_search.py +++ b/src/spotforecast2/model_selection/spotoptim_search.py @@ -877,7 +877,7 @@ def build_warm_start_x0( ``SpotOptimStrategy.prepare_forecaster``). forecaster: The pre-tuning forecaster; its ``estimator`` supplies the starting values for the numeric hyperparameter dimensions. - lags_seed: The lag configuration to seed (e.g. ``config.lags_consider``). + lags_seed: The lag configuration to seed (e.g. ``config.warm_start_lags``). Returns: A 1-D float array of length ``len(var_name)``, or ``None`` when the diff --git a/src/spotforecast2/multitask/strategies.py b/src/spotforecast2/multitask/strategies.py index 118f2606..f0352792 100644 --- a/src/spotforecast2/multitask/strategies.py +++ b/src/spotforecast2/multitask/strategies.py @@ -241,9 +241,10 @@ def prepare_forecaster( task: A `BaseTask` (or compatible) instance that supplies ``cv_ts``, ``config``, ``logger``, ``save_tuning_results``, and ``create_forecaster``. The config must expose - ``n_trials_spotoptim``, ``n_initial_spotoptim``, - ``random_state``, ``warm_start_lags``, and optionally - ``lags_consider`` and the TensorBoard knobs described + ``n_trials_spotoptim``, ``n_initial_spotoptim``, and + ``random_state``; optionally ``warm_start_lags`` (the seed + lag set, sf2-safe >= 22.0.0; ``None``/empty = cold start), + ``max_time_spotoptim``, and the TensorBoard knobs described above. target: Target column name; forwarded to ``task.create_forecaster`` and ``task.save_tuning_results``. @@ -288,7 +289,7 @@ def prepare_forecaster( n_trials_spotoptim=5, n_initial_spotoptim=3, random_state=0, - warm_start_lags=False, + warm_start_lags=None, ) task = types.SimpleNamespace( config=cfg, @@ -318,15 +319,15 @@ def prepare_forecaster( search_space = self.search_space or _default_spotoptim_search_space() cv = task.cv_ts(y_train) - # Warm start: inject ``lags_consider`` as a candidate lag set and seed - # the optimizer's first evaluation with it. Only dict search spaces - # with a ``"lags"`` list are eligible; anything else falls through to a - # normal cold-start run. + # Warm start: ``config.warm_start_lags`` (sf2-safe >= 22.0.0) is the + # seed lag set itself — it is injected as a search-space candidate and + # seeds the optimizer's first evaluation. ``None``/empty disables the + # warm start. Only dict search spaces with a ``"lags"`` list are + # eligible; anything else falls through to a normal cold-start run. kwargs_spotoptim: Dict[str, Any] = {} - lags_seed = getattr(task.config, "lags_consider", None) + lags_seed = getattr(task.config, "warm_start_lags", None) if ( - getattr(task.config, "warm_start_lags", False) - and lags_seed + lags_seed and isinstance(search_space, dict) and isinstance(search_space.get("lags"), list) ): diff --git a/tests/test_multitask_strategies.py b/tests/test_multitask_strategies.py index ce6354a3..1753d82a 100644 --- a/tests/test_multitask_strategies.py +++ b/tests/test_multitask_strategies.py @@ -75,7 +75,7 @@ def _make_fake_task(**config_extra): n_trials_spotoptim=2, n_initial_spotoptim=1, random_state=0, - warm_start_lags=False, + warm_start_lags=None, **config_extra, ) return types.SimpleNamespace( diff --git a/tests/test_spotoptim_warm_start.py b/tests/test_spotoptim_warm_start.py index b8f8b919..951309bf 100644 --- a/tests/test_spotoptim_warm_start.py +++ b/tests/test_spotoptim_warm_start.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2026 bartzbeielstein # SPDX-License-Identifier: AGPL-3.0-or-later -"""Tests for warm-starting the SpotOptim search with ``lags_consider``. +"""Tests for warm-starting the SpotOptim search with ``warm_start_lags``. Covers the ``build_warm_start_x0`` helper (full-dim seed point, clipping, graceful ``None`` cases) and an end-to-end check that the seeded lag @@ -112,8 +112,7 @@ def test_warm_start_x0_round_trips_to_seed(): class _MockConfig: - warm_start_lags = True - lags_consider = [1, 2, 24] + warm_start_lags = [1, 2, 24] random_state = 1 n_trials_spotoptim = 4 n_initial_spotoptim = 3 @@ -141,7 +140,7 @@ def create_forecaster(self, target): def test_strategy_injects_seed_and_forwards_x0(monkeypatch): - """The strategy injects lags_consider as a candidate and forwards x0.""" + """The strategy injects warm_start_lags as a candidate and forwards x0.""" from spotforecast2.multitask.strategies import SpotOptimStrategy captured = {} @@ -186,7 +185,7 @@ def fake_search(**kwargs): def test_strategy_no_x0_when_flag_disabled(monkeypatch): - """With warm_start_lags False, no x0 is passed and lags is untouched.""" + """With warm_start_lags None, no x0 is passed and lags is untouched.""" from spotforecast2.multitask.strategies import SpotOptimStrategy captured = {} @@ -207,7 +206,7 @@ def fake_search(**kwargs): ) task = _MockTask() - task.config.warm_start_lags = False + task.config.warm_start_lags = None y = pd.Series( np.sin(np.arange(400) * 2 * np.pi / 24), index=pd.date_range("2022-01-01", periods=400, freq="h"), diff --git a/uv.lock b/uv.lock index fd654820..ddac7dee 100644 --- a/uv.lock +++ b/uv.lock @@ -3491,7 +3491,7 @@ wheels = [ [[package]] name = "spotforecast2" -version = "7.0.0" +version = "8.0.0rc1" source = { editable = "." } dependencies = [ { name = "astral" }, @@ -3571,7 +3571,7 @@ requires-dist = [ { name = "safety", marker = "extra == 'dev'", specifier = ">=3.0.0" }, { name = "scikit-learn", specifier = ">=1.8.0" }, { name = "shap", specifier = ">=0.49.1" }, - { name = "spotforecast2-safe", specifier = ">=21.2.0,<22" }, + { name = "spotforecast2-safe", specifier = ">=22.0.0,<23" }, { name = "spotoptim", extras = ["torch"], specifier = ">=1.0.0,<2" }, { name = "tqdm", specifier = ">=4.67.2" }, { name = "ty", marker = "extra == 'dev'", specifier = ">=0.0.29" }, @@ -3590,7 +3590,7 @@ dev = [ [[package]] name = "spotforecast2-safe" -version = "21.2.0" +version = "22.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "astral" }, @@ -3607,9 +3607,9 @@ dependencies = [ { name = "statsmodels" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a5/48/e12f37462beae893f901751648f8b830475cd58d19d8e8e9703236cd25c8/spotforecast2_safe-21.2.0.tar.gz", hash = "sha256:1b181bc157a0765b15a5348831902465d356b21cba3b6ad36b57d54f6a51b07c", size = 20630614, upload-time = "2026-06-10T16:28:37.526Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/89/00f24736a21666be93e6e59fdc0958e497178eb890ab5cefadd88e6d980f/spotforecast2_safe-22.0.0.tar.gz", hash = "sha256:f2c411e074b41a0421185c58a086b6e61b90d4d4fcab9860a93fa7faa09e18bf", size = 20630930, upload-time = "2026-06-10T23:51:54.156Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/73/0537ab7ce84308bfd49b04b365fde4a58ef2f8b3d40b415b8963777b71c7/spotforecast2_safe-21.2.0-py3-none-any.whl", hash = "sha256:1a80b4629062a0600b108416e93a07f75e1d31cf7bec0bcd02cb77089826e11c", size = 20696409, upload-time = "2026-06-10T16:28:34.959Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5a/d2bd77a6233ea08df61903391ab27bfe810dc4e107c2f452500e04c73f97/spotforecast2_safe-22.0.0-py3-none-any.whl", hash = "sha256:18906e1371cd93f03af4dbead15bb94db7945624aa24777cf12c06fe2d951e23", size = 20696710, upload-time = "2026-06-10T23:51:51.911Z" }, ] [[package]]