Skip to content

[GP-02] Warm-start: reuse solver basis for heuristic solves and sequential inter-block resolution #272

Description

@aoustry

Process GP-02 — Triggered by internal initiative to fix, improve, or extend GemsPy. Not driven by a new GEMS Language version.

Deviation from standard workflow — Step 3: extended impact analysis on results is required. You must explicitly state whether solver results are expected to change, and if so, document the reasoning.

Full design doc (current, updated in place as decisions are made): docs/design/warm-start-plan.md on branch claude/gemspy-warmstart-possibilities-5qo718.

Related: #273 (ambitious follow-on — persistent rolling-window model for _run_sequential, gated on this issue's linopy spike and on profiling). This issue should ship first regardless of whether #273 is pursued.

Affected Component

Optimization problem construction (simulation/) — plus session orchestration (gems_runner/session/session.py) and config schema (gems_craft/optim_config/parsing.py).

Type of Change

Minor: backward-compatible new feature, opt-in via two new config flags (warm-start-heuristic, warm-start-interblock), both defaulting to false. No existing study, config, or test changes behavior by default.

Description

GemsPy never reuses an LP/MIP basis between solves today. Two places re-solve a structurally near-identical problem and can benefit from solver warm-start, the same way Antares Simulator's useOptim1BasisInOptim2 and useOptim1BasisInNextWeek flags do:

  1. Two-stage heuristic solveSimulationSession._run_block solves the same OptimizationProblem twice (once, then again after apply_thermal_heuristics tightens a few variable bounds). Living inside _run_block means this benefits frontal, sequential, and parallel resolution modes uniformly, with no per-mode branching.
  2. Inter-block chaining_run_sequential builds and solves a fresh linopy.Model per time block; block-to-block the constraint structure is identical, only the data differs. Scoped to _run_sequential only_run_parallel is deliberately excluded: its blocks are meant to stay independent so a future genuinely-concurrent implementation (process/thread pool) doesn't inherit an artificial ordering dependency that would need undoing later.

Mechanism: linopy.Model.solve() already exposes basis_fn/warmstart_fn (confirmed working for HiGHS via io_api="direct"), so OptimizationProblem.solve() needs no changes — only new orchestration in session.py (a _BasisChain helper with a verified O(1) space bound, RAM-backed temp files via /dev/shm with a portable fallback, and a defensive wrapper for solvers that don't support warmstarting).

Before implementing as file-based: linopy 0.8.0/0.9.0 shipped native persistent-Solver support with in-place updates (verified against the installed source, not just release notes) that looks purpose-built for the heuristic-solve case specifically, since _run_block already reuses one linopy.Model for both solves. GemsPy is pinned to 0.6.6, but the dependency floor ("linopy>=0.6") already permits the upgrade. Recommend a short compatibility spike (bump to 0.9.x, verify heuristic_runner.py's direct variable-bound mutation and optimization.py's _MergedGroupVariable still behave correctly) before building the file-based design — if it pans out, §5 of the plan gets meaningfully simpler. This spike also gates #273.

Open questions carried into implementation (see plan §4 and prior-art review):

  • Solver support: generic-with-fallback (try any solver, catch NotImplementedError) vs. an explicit whitelist matching Antares's own conservative design (only SIRIUS/XPRESS there) — not resolved in the plan, flagged for the implementation PR.
  • The heuristic-solve benefit is conditional: it behaves as a clean LP-to-LP warmstart only when every component uses RELAXED/HEURISTIC integer strategy. Since IntegerStrategyId defaults to EXACT, mixed-strategy studies make both solves MIPs, and warmstart only re-warms the root LP relaxation — weaker, but still worth having.

Adjacent, near-free win scoped into the same implementation: simulation_table.py already declares a basis_status output column that's currently a dead placeholder (None) — the basis-extraction machinery this feature needs can populate it, matching Antares's own documented modeler output format.

Results Impact

No change to the optimal objective value is intended — warm-start only affects how fast a solve converges, not what problem is being solved. However: per-timestep dispatch values are not guaranteed to be bit-identical between warm-start-on and warm-start-off runs on degenerate LPs (multiple exactly-tied optimal bases — routine in energy dispatch: symmetric generators, zero-marginal-cost curtailment ties). Antares hit this in production twice (a real shipped bugfix to clear the basis at MC-year boundaries, and a test-infrastructure fix to exclude basis status from result comparisons) — this is why the plan's validation strategy leads with objective-value equality rather than per-timestep equality.

Validation Strategy

  • Objective-value equality (tight relative tolerance) between warm-start-on/off runs, for both flags independently and combined — the primary correctness check.
  • Per-timestep equality as a secondary check, gated to a test study verified free of dispatch degeneracy (documented explicitly, so future test-data changes don't reintroduce flakiness).
  • Space-boundedness test: assert at most one basis file exists per chain at any point during a multi-block _run_sequential run.
  • Plumbing unit tests for _BasisChain and the solver-fallback wrapper.
  • Dedicated regression test on a mixed-integer-strategy study (weaker-benefit case) confirming warm-start doesn't change the objective there either.
  • Full detail in plan §7.

Process Checklist

Step 1 — Issue Creation

  • Issue created and linked to process GP-02

Step 2 — Triage

  • Process confirmed applicable
  • Assigned to responsible contributor
  • Priority and milestone set (if applicable)

Step 3 — Impact Analysis ⚠️ extended results analysis required

  • Affected modules identified
  • Results impact explicitly stated: change intended / no change intended
  • If results expected to change: before/after difference described and justified
  • Breaking vs backward-compatible change determined

Step 4 — Implementation

  • linopy 0.9.x compatibility spike completed (heuristic-solve case) — determines whether §5 uses the native persistent-Solver API or the file-based design
  • Code changes implemented
  • Backward compatibility maintained (both flags default false)

Step 5 — Testing & Validation

  • Objective-value equivalence confirmed via tests (warm-start on vs. off)
  • Per-timestep equivalence confirmed on a degeneracy-free reference study
  • Unit tests cover changed components (_BasisChain, fallback wrapper)

Step 6 — CI Validation

  • Type checking passes (mypy)
  • Formatting passes (black, isort)
  • All tests pass in CI (pytest)

Step 7 — Review & Merge

  • PR reviewed; documentation changes included

Step 8 — Versioning

  • pyproject.toml version bumped

Step 9 — Supporting Files

  • AGENTS.md reviewed for impact and updated if needed

Step 10 — Release

  • If a release is needed: follow the release process in the Developer Guidelines

Metadata

Metadata

Assignees

No one assigned

    Labels

    GP-02Internal GemsPy bug fixes, features, or improvements (not driven by a GEMS Language release)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions