piecewise_ragged¶
Dispatch where each generator's cost curve has as many breakpoints as its own data.
✔ Agrees with hand-written linopy 0.10.0 — objective 426, matched to
rtol=1e-09.
The problem¶
The hydro unit here has two breakpoints, the coal plant three, the gas turbine four. Nothing in the model has to know which is longest.
The weights exist only where a curve has a breakpoint, so the hydro unit carries two weights rather than four, and no curve is padded.
The same model, as math
Least-cost dispatch where each generator's cost curve has as many breakpoints as its data gives it — two for the hydro unit, four for the gas turbine — rather than as many as the longest curve in the system.
Sets¶
| Symbol | Meaning |
|---|---|
| \(\mathcal{T}\) | index \(t\) — snapshot — dispatch periods |
| \(\mathcal{G}\) | index \(g\) — generator — dispatchable units |
| \(\mathcal{B}\) | index \(b\) — bp — breakpoints, as many as the longest curve needs |
Parameters¶
| Symbol | Meaning |
|---|---|
| \(\mathrm{p}^{\mathrm{max}}\) | p_max over \(\mathcal{G}\) — maximum dispatch |
| \(\mathrm{load}\) | load over \(\mathcal{T}\) — demand to be met |
| \(\mathrm{bp\_x}\) | bp_x over \(\mathcal{G} \times \mathcal{B}\) — breakpoint dispatch levels, one curve per generator and no two the same length |
| \(\mathrm{bp\_y}\) | bp_y over \(\mathcal{G} \times \mathcal{B}\) — cost at each breakpoint |
Variables¶
| Symbol | Meaning |
|---|---|
| \(p\) | p over \(\mathcal{T} \times \mathcal{G}\) — dispatched power |
| \(\mathit{op\_cost}\) | op_cost over \(\mathcal{T} \times \mathcal{G}\) — operating cost, piecewise-linear in dispatch |
Upright is what the data supplies — a parameter such as \(\mathrm{p}^{\mathrm{max}}\), a coordinate map, a label — and italic is what the solver chooses, such as \(p\). An index is italic too, being what a quantifier chooses, and a set is script.
\(t \boxminus_{v} k\) denotes translation with \(v\) standing where index \(t-k\) leaves the dimension (shift(edge=v)), so the row at that boundary is built and carries \(v\) rather than being dropped.
Objective¶
Subject to¶
balance
cost_curve
Variable domains¶
p
op_cost
Assumptions¶
cost_curve_complete
cost_curve_increasing
cost_curve_curvature
cost_curve_contiguous
The tabs start from the instance's tables — one frame per parameter.
description: >-
Least-cost dispatch where each generator's cost curve has as many breakpoints
as its data gives it — two for the hydro unit, four for the gas turbine —
rather than as many as the longest curve in the system.
dimensions:
snapshot:
description: dispatch periods
dtype: int
generator:
description: dispatchable units
dtype: str
bp:
description: breakpoints, as many as the longest curve needs
dtype: int
parameters:
p_max:
description: maximum dispatch
dims: [generator]
load:
description: demand to be met
dims: [snapshot]
bp_x:
description: breakpoint dispatch levels, one curve per generator and no two the same length
dims: [generator, bp]
bp_y:
description: cost at each breakpoint
dims: [generator, bp]
variables:
p:
description: dispatched power
dims: [snapshot, generator]
bounds:
lower: 0
upper: p_max
op_cost:
description: operating cost, piecewise-linear in dispatch
dims: [snapshot, generator]
bounds:
lower: 0
piecewise:
cost_curve:
description: >-
each unit's own curve — `points: bp_x` says a curve runs as far as its own
breakpoints do, so the hydro unit pays for two weights and the gas turbine
for four
over: bp
points: bp_x
links:
- [p, bp_x]
- [op_cost, bp_y, ">="]
method: convex
constraints:
balance:
description: every period's demand is met
dims: [snapshot]
expression: sum(p, over=generator) == load
objective:
sense: minimize
description: total operating cost, taken off each unit's own curve
expression: sum(sum(op_cost, over=generator), over=snapshot)
The model-building half of examples/ports/references/linopy/piecewise_ragged.py:
def build(tables: dict[str, pd.DataFrame]) -> linopy.Model:
"""The instance's tables as a linopy model, row for row.
``tables`` is the same mapping the specsolve call attaches as ``sources``.
"""
p_max: pd.Series = tables['p_max'].set_index('generator')['value']
load: pd.Series = tables['load'].set_index('snapshot')['value']
reach = tables['bp_x'].groupby('generator')['value'].max().reindex(p_max.index)
m = linopy.Model()
p = m.add_variables(lower=0, upper=reach, coords=[load.index, p_max.index], name='p')
op_cost = m.add_variables(lower=0, coords=[load.index, p_max.index], name='op_cost')
for g, lines in segments(tables).items():
for k, (slope, intercept) in enumerate(lines):
m.add_constraints(
op_cost.sel(generator=g) - slope * p.sel(generator=g) >= intercept,
name=f'chord_{g}_{k}',
)
m.add_constraints(p.sum('generator') == load, name='balance')
m.add_objective(op_cost.sum())
return m
examples/piecewise_ragged.yaml · back to all models