Decomposition (Benders)#
el1xr_opt can solve the investment + operating problem as a single monolithic model
(the default) or by Benders decomposition. The decomposition reaches the same
optimum as the monolith; its value is letting a large problem be built and solved in
independent blocks. The code is in oM_Decomposition.py; the design is in
docs/decomposition.md.
Block structure#
The problem is block-angular:
the investment (capacity-sizing) decisions are the first stage, shared by every block (the Benders master variables);
the operating problem separates by
(period, scenario)– and, for long horizons, by contiguous time windows – given the investment;storage couples time, so when the horizon is split, the storage inventory at each window boundary is the linking variable. There is one boundary inventory per stored carrier:
Se(electricity),Sh(hydrogen) andSt(the heat thermal store). The heat store is now coupled across windows like a battery: the incoming boundary replaces the window’s first-level store balance, and the outgoing boundary ties the window’s last level to the master copy.
Solve entry points#
el1xr_benders(dir, case, date, ...)– master holds the investment build fractions; one operating subproblem per(period, scenario)block with the investment fixed (the fixing-constraint duals give the optimality cuts). Validated to reach the exact monolithic optimum.el1xr_temporal_benders(dir, case, date, n_time_blocks=...)– splits one operating horizon into time windows coupled by the storage inventory at each boundary, which the master holds (Se/Sh/Stfor electricity / hydrogen / heat storage). The heat operating cost (heat-not-served plus heat-generator running cost) is a per-load-level sum, so it decomposes per window and is added to each window’s recourse. Validated against the binary monolith for several block counts.
Some per-(period, scenario) charges depend on the whole horizon and do not split by
window (a naive split would double-count them). These are handled by a registry-driven
mechanism rather than hard-coded names. seed_horizon_coupling (in oM_Features)
reads the structure model and declares each such charge:
a constant charge over the horizon (
register_horizon_constant) – for example the fixed network fee – is counted once in the master and removed from every window’s recourse;a peak / threshold charge (
register_horizon_threshold) – the sum of the N largest grid imports per month – is rewritten as a threshold LP whose per-month threshold is a master linking variable;an unsupported charge (
register_horizon_unsupported) raises a clear error instead of returning a wrong objective. A Daily power-tariff peak is marked unsupported.
A safety net backs this up: the split compares the registered "ps" cost and revenue
terms against TEMPORAL_HANDLED_PS_COST / TEMPORAL_HANDLED_PS_REV and refuses an
unrecognised one, so a charge that is added to the objective but not described is caught
rather than silently dropped.
benders_solve(...)– the generic multi-cut L-shaped method the two el1xr entry points call; reusable for any two-stage stochastic program.
Feasibility is guaranteed for any first-stage decision with an elastic penalty on the operating constraints, so optimality cuts suffice (no separate feasibility-cut pass).
Scope of the temporal MVP#
el1xr_temporal_benders is a minimum-viable version with three limits, each enforced
by a raise rather than a silent wrong answer:
it handles a single
(period, scenario);it assumes hourly storage (cycle time step = 1), so each window boundary is one inventory value per storage unit;
it supports Hourly power tariffs only; a Daily power-tariff peak charge raises.
Parallelism#
The subproblems are independent given the master decision, so they can be solved at
once. Pyomo solvers are not thread-safe, so the parallelism is by process:
BendersConfig.n_workers > 1 starts a pool of worker processes that each build and
reuse their share of the blocks across iterations. The result is identical to the
sequential solve.
When it pays off#
Decomposition wins when the per-block work dominates the coordination overhead – a
combinatorial first stage (unit-commitment binaries) or a problem too large to hold in
one model. On small LPs the monolith is faster (one efficient solve beats many
round-trips). See the benchmarks under benchmarks/ and docs/decomposition.md.