Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/model_selection/spotoptim_intro.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,10 @@ results, optimizer = spotoptim_search_forecaster(
cfg = ConfigEntsoe(...)
cfg.tensorboard_log = True
cfg.tensorboard_path = "runs/my_tuning_run" # optional; defaults to runs/<stamp>
cfg.tensorboard_clean = False # True wipes old runs/ subdirs first
# tensorboard_clean defaults to True when logging is enabled: the log
# directory is wiped at search start so each run gets a fresh dashboard.
# Set cfg.tensorboard_clean = False to keep accumulating event files —
# do that (or use per-task paths) when several tasks share one directory.
```

Then watch the run live:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ dependencies = [
"scikit-learn>=1.8.0",
"shap>=0.49.1",
"spotforecast2-safe>=18.0.0,<19",
"spotoptim>=0.12.8",
"spotoptim>=0.12.9",
"tqdm>=4.67.2",
]

Expand Down
16 changes: 13 additions & 3 deletions src/spotforecast2/multitask/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,14 @@ def prepare_forecaster(
SpotOptim constructor and the ongoing search can be watched with
``tensorboard --logdir <path>`` (defaults to ``runs/``). Set them
as plain attributes, e.g. ``cfg.tensorboard_log = True`` — no
config-class change is required. Live per-eval scalars under
parallel tuning (``n_jobs_spotoptim != 1``) require
``spotoptim >= 0.12.8``.
config-class change is required. When logging is enabled and
``tensorboard_clean`` is unset, it defaults to ``True`` so every
run starts with a fresh dashboard (the configured log directory
is wiped at search start); set ``cfg.tensorboard_clean = False``
to keep accumulating event files instead — do that, or give each
task its own ``tensorboard_path``, when several tasks share one
log directory. Live per-eval scalars under parallel tuning
(``n_jobs_spotoptim != 1``) require ``spotoptim >= 0.12.8``.

Args:
task: A `BaseTask` (or compatible) instance that supplies ``cv_ts``,
Expand Down Expand Up @@ -347,6 +352,11 @@ def prepare_forecaster(
for key in ("tensorboard_log", "tensorboard_path", "tensorboard_clean")
if getattr(task.config, key, None) is not None
}
if tb_kwargs.get("tensorboard_log") and "tensorboard_clean" not in tb_kwargs:
# Fresh dashboard per run: wipe the configured log directory before
# the search starts (spotoptim >= 0.12.9 cleans tensorboard_path
# itself). Opt out with ``cfg.tensorboard_clean = False``.
tb_kwargs["tensorboard_clean"] = True
if tb_kwargs:
kwargs_spotoptim.update(tb_kwargs)
task.logger.info(" SpotOptim TensorBoard: %s", tb_kwargs)
Expand Down
61 changes: 59 additions & 2 deletions tests/test_multitask_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,69 @@ def fake_search_forecaster(*args, **kwargs):
ks = captured["kwargs_spotoptim"]
assert ks["tensorboard_log"] is True
assert ks["tensorboard_path"] == "runs/test-tb"
# Unset attribute must be skipped so SpotOptim's default stays in charge.
assert "tensorboard_clean" not in ks
# With logging enabled and tensorboard_clean unset, the strategy
# defaults it to True so every run starts with a fresh dashboard.
assert ks["tensorboard_clean"] is True
assert isinstance(tuned, _FakeForecaster)
assert tuned.params == {"alpha": 1.0}


def test_spotoptim_strategy_respects_explicit_tensorboard_clean_false(monkeypatch):
"""An explicit tensorboard_clean=False must not be overridden."""
import pandas as pd

import spotforecast2.model_selection as ms

captured = {}

def fake_search_forecaster(*args, **kwargs):
captured["kwargs_spotoptim"] = kwargs.get("kwargs_spotoptim")
results = pd.DataFrame({"params": [{"alpha": 1.0}], "lags": [[1, 2]]})
return results, object()

monkeypatch.setattr(ms, "spotoptim_search_forecaster", fake_search_forecaster)

task = _make_fake_task(
tensorboard_log=True,
tensorboard_path="runs/test-tb",
tensorboard_clean=False,
)
SpotOptimStrategy(search_space={"alpha": (0.1, 1.0)}).prepare_forecaster(
task, "A", _FakeForecaster(), y_train=None
)

ks = captured["kwargs_spotoptim"]
assert ks["tensorboard_log"] is True
assert ks["tensorboard_clean"] is False


def test_spotoptim_strategy_no_clean_default_without_logging(monkeypatch):
"""tensorboard_clean is only defaulted when tensorboard_log is enabled."""
import pandas as pd

import spotforecast2.model_selection as ms

captured = {}

def fake_search_forecaster(*args, **kwargs):
captured["kwargs_spotoptim"] = kwargs.get("kwargs_spotoptim")
results = pd.DataFrame({"params": [{"alpha": 1.0}], "lags": [[1, 2]]})
return results, object()

monkeypatch.setattr(ms, "spotoptim_search_forecaster", fake_search_forecaster)

# tensorboard_log=False is forwarded (it is not None) but must not
# trigger the clean default.
task = _make_fake_task(tensorboard_log=False)
SpotOptimStrategy(search_space={"alpha": (0.1, 1.0)}).prepare_forecaster(
task, "A", _FakeForecaster(), y_train=None
)

ks = captured["kwargs_spotoptim"]
assert ks["tensorboard_log"] is False
assert "tensorboard_clean" not in ks


def test_spotoptim_strategy_no_tensorboard_attrs_no_kwargs(monkeypatch):
"""Without tensorboard config attrs, no tensorboard keys are forwarded."""
import pandas as pd
Expand Down
10 changes: 5 additions & 5 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading