piecewise¶
Per-generator convex cost curves, expanded into a λ-formulation.
The problem¶
Each generator gets its own breakpoint list, so the curve varies per unit — something a flat breakpoint list cannot express:
The model¶
The same model, as math
Sets¶
| Symbol | Meaning |
|---|---|
| \(\mathcal{T}\) | index \(t\) --- snapshot --- dispatch periods |
| \(\mathcal{G}\) | index \(g\) --- generator --- dispatchable units |
| \(\mathcal{K}\) | index \(k\) --- bp --- breakpoints of the cost curve |
Parameters¶
| Symbol | Meaning |
|---|---|
| \(p^{\mathrm{max}}\) | p_max over \(\mathcal{G}\) --- maximum dispatch |
| \(\mathit{load}\) | load over \(\mathcal{T}\) --- demand to be met |
| \(x\) | bp_x over \(\mathcal{G} \times \mathcal{K}\) --- breakpoint dispatch levels |
| \(y\) | bp_y over \(\mathcal{G} \times \mathcal{K}\) --- cost at each breakpoint |
Variables¶
| Symbol | Meaning |
|---|---|
| \(p\) | p over \(\mathcal{T} \times \mathcal{G}\) --- dispatched power |
| \(\mathrm{cost}\) | op_cost over \(\mathcal{T} \times \mathcal{G}\) --- operating cost, piecewise-linear in dispatch |
| \(\lambda\) | cost_curve_lam over \(\mathcal{T} \times \mathcal{G} \times \mathcal{K}\) --- convex-combination weight on breakpoint \(k\) |
Objective¶
total_cost
Subject to¶
balance
cost_curve_convexity
cost_curve_link0
cost_curve_link1
Variable domains¶
p
op_cost
cost_curve_lam
# Dispatch with piecewise-linear generation costs via the piecewise: block.
#
# Each generator has its own convex cost curve: the breakpoint parameters
# carry [generator, bp], so curves vary per generator — something flat
# breakpoint lists cannot express. With convex: true the expansion emits no
# binaries (pure-LP convex hull, exact for convex curves under minimisation)
# and the model stays relational-eligible as an LP. Drop convex: true for
# nonconvex curves — the expansion then adds segment binaries + adjacency,
# and the model becomes a (still relational-eligible) MILP.
dimensions:
snapshot:
dtype: int
generator:
dtype: str
bp:
dtype: int
parameters:
p_max:
dims: [generator]
load:
dims: [snapshot]
bp_x:
dims: [generator, bp] # per-generator breakpoint positions
bp_y:
dims: [generator, bp] # per-generator cost at each breakpoint
variables:
p:
foreach: [snapshot, generator]
bounds:
lower: 0
upper: p_max
op_cost:
foreach: [snapshot, generator]
bounds:
lower: 0
piecewise:
cost_curve:
over: bp
links:
- [p, bp_x]
- [op_cost, bp_y]
convex: true
constraints:
balance:
foreach: [snapshot]
expression: sum(p, over=generator) == load
objectives:
total_cost:
sense: minimize
expression: op_cost
What it exercises¶
piecewise: is a declaration, not a helper — it expands before lowering
into the λ-formulation above. With convex: true the expansion emits no
binaries at all: the convex hull is exact for a convex curve under
minimisation, so the model stays a pure LP. Drop convex: true and the
expansion adds segment binaries and adjacency constraints, and the model
becomes a MILP that is still entirely inside the relational subset.
By the time the logical plan exists there is nothing left called piecewise — which is why the construct matrix reads it from the surface declaration.
examples/piecewise.yaml · back to all models