Skip to content

Investigate continuous-action/exact-piecewise-linear reformulation of the DP to eliminate discretization interpolation error #276

Description

@johanzander

Summary

Tracked follow-up from #275 (DP holds charge for a known-worse future price
on extended horizons) and the design doc
docs/superpowers/specs/2026-07-12-dp-continuous-path-reconstruction-fix-design.md
(Option C there). Not being implemented as part of the #275 fix — this is a
larger algorithm redesign, filed here so the idea isn't lost.

The problem it would solve

The DP's backward induction (core/bess/dp_battery_algorithm.py) only
computes its value function V[t, i] at a fixed grid of SOE points
(SOE_STEP_KWH = 0.1) and searches a fixed grid of power actions
(POWER_STEP_KW = 0.2). The real battery's actual SOE almost never lands
exactly on a grid point, so reconstructing the actual schedule (Step 2 of
optimize_battery_schedule) has to interpolate V at off-grid states.
V has genuine kinks (slope discontinuities) wherever the optimal action
changes character — e.g. crossing BATTERY_EXPORT_THRESHOLD_KWH's
self-throttling cutoff, or a price threshold where exporting stops being
worth it — and linear interpolation across a kink gives a wrong answer.
That's the confirmed root cause of #275, and a smaller, previously-shipped
version of the same mechanism (2026-07-06 guardrail-removal doc).

Two options already tried (grid-snap vs. interpolated recompute) each win
in a different regime (single-day vs. multi-day horizons) — see #275 and the
2026-07-12 design doc for the empirical evidence. Neither eliminates the
error; they just move it around.

What this issue proposes investigating

Remove the fixed grid as the source of the mismatch entirely, via one or
both of:

  1. Solve for the optimal power level analytically instead of grid search.
    For a fixed price/state, one-period reward is mostly piecewise-linear in
    the charge/discharge power, so the true per-period optimum could be
    computed directly (root-finding / closed-form) at the actual continuous
    SOE, instead of testing ~150 discrete power values.
  2. Track V as an exact piecewise-linear function of SOE, not a sampled
    grid.
    Classical LP-sensitivity result: for linear dynamics and
    piecewise-linear reward, the true value function at each time step is
    itself exactly piecewise-linear, with a small number of real breakpoints
    — not the artificial ones introduced by uniform 0.1 kWh sampling.
    Tracking those breakpoints exactly would make "interpolation" exact
    rather than approximate, since there'd be no hidden kink to interpolate
    across.

Why this is deferred, not part of #275's fix

  • Real algorithm redesign, not a patch — touches _run_dynamic_programming's
    core loop and _compute_reward's structure.
  • Needs proof that the reward function is piecewise-linear/concave enough
    for either approach to work cleanly; BATTERY_EXPORT_THRESHOLD_KWH
    (dp_battery_algorithm.py:344) is itself a hard discontinuity worth
    checking against this assumption.
  • No existing prototype — 2026-07-06 already flagged this direction as out
    of scope for that PR, for the same reasons.
  • Interacts with perf: vectorize DP backward-induction hot loop in dp_battery_algorithm.py #236 (DP hot-loop perf) either way, depending on which
    approach is chosen.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions