Fix test suite hangs caused by degenerate MILP fixtures
CI / test (push) Successful in 12s

Two tests were spinning up real CBC solves over 96-half-hour windows
with long runs of exactly-repeated prices (e.g. [0.0] * 40). That gives
the LP relaxation a huge set of economically indistinguishable ways to
spread a trade, which the MILP fallback's branch-and-bound then wastes
enormous effort disambiguating (47k+ nodes without closing the gap,
confirmed by running CBC verbosely). Real Attachment 2 data has no such
flat runs and solves in ~0.1s/window; a synthetic sine wiggle wasn't
enough either, since neighbouring half-hours stayed too similar.

Fixes, matched to what each test actually needs:
- The 9 validator tests only need *some* structurally valid schedule to
  mutate; they were deriving it by running the real optimiser once per
  test. Replaced with make_valid_schedule(), built directly from the
  model's own energy-balance formulas -- no solver involved, and it's
  now a true unit test of validate_schedule() in isolation.
- The rolling-horizon carry-forward test was exercising the mechanism
  at full production scale (48h window / 24h commit) when a 2h/1h
  window proves the same boundary-carrying behaviour with a trivial
  MILP, regardless of price structure.
- Fixed a genuine tie in test_optimum_uses_whichever_market_pays_more:
  two equal-price hours with just enough stored energy for one meant
  either market was a valid optimum. Sized the charge phase so delivery
  must split across both hours, pinning a unique answer.

Full suite: 38 passed in ~1.3s (previously hung indefinitely on CI and
locally within seconds of the same wall-clock variance CBC shows on
degenerate MIPs).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 16:10:55 +01:00
co-authored by Claude Sonnet 5
parent 298724a9d3
commit afa02864a7
3 changed files with 91 additions and 27 deletions
+5 -10
View File
@@ -5,22 +5,17 @@ from __future__ import annotations
import numpy as np
import pandas as pd
import pytest
from conftest import prices_frame
from conftest import make_valid_schedule
from battery_dispatch.config import RunConfig
from battery_dispatch.optimiser import run_rolling_horizon
from battery_dispatch.validation import validate_schedule
@pytest.fixture
def schedule(spec) -> pd.DataFrame:
"""A genuine, valid two-day schedule to mutate in each test."""
prices = prices_frame(
[0.0] * 20 + [90.0] * 8 + [10.0] * 20 + [5.0] * 16 + [120.0] * 8 + [40.0] * 24,
[30.0] * 48,
)
result, _, _ = run_rolling_horizon(prices, spec, RunConfig())
return result
"""A genuine, valid schedule to mutate in each test. See
``conftest.make_valid_schedule`` for why this isn't a real optimiser run.
"""
return make_valid_schedule(spec)
def test_a_valid_schedule_passes_every_check(schedule, spec):