Unit commitment#
This model adds a commitment decision and a start-up ramp to least-cost
dispatch. Read previous_status first, then ramp_up: the state a unit
carries into a snapshot has three regimes, stated once as a
cases: block, and ramp_up reads it
the way it reads a parameter. The block prints once below, under
Definitions.
description: >-
Unit commitment with a start-up ramp, the formulation `cases:` exists for.
The state a unit carries into a snapshot has three regimes — a unit that is
never off, the first snapshot, and every later one — and writing them at the
constraint would fork `ramp_up` three ways. With the regimes named once, the
inequality is written once.
dimensions:
snapshot: { dtype: int, description: dispatch periods }
generator: { description: generating units }
parameters:
committable: { dims: [generator], dtype: bool, description: whether the unit may be switched off }
status_initial: { dims: [generator], description: whether the unit was running before the horizon }
capacity: { dims: [generator], description: installed capacity }
min_output: { dims: [generator], description: output floor while running }
ramp_limit: { dims: [generator], description: how far output may move between snapshots while running }
start_up_limit: { dims: [generator], description: how far it may move in the snapshot it starts in }
load: { dims: [snapshot], description: demand to be met }
cost: { dims: [generator], description: marginal cost }
variables:
dispatch:
description: output of a generator in a snapshot
dims: [snapshot, generator]
bounds: { lower: 0, upper: capacity }
status:
description: whether the unit is running in a snapshot
dims: [snapshot, generator]
domain: binary
expressions:
previous_status:
description: the commitment state a unit carries into a snapshot
dims: [snapshot, generator]
cases:
always_on:
when: "not committable"
expression: 1
boundary:
when: "committable and position(snapshot) == 0"
expression: status_initial
otherwise: shift(status, along=snapshot, offset=1)
constraints:
power_balance:
dims: [snapshot]
expression: sum(dispatch, over=generator) == load
upper:
description: a unit that is not running produces nothing
dims: [snapshot, generator]
expression: dispatch <= status * capacity
lower:
description: and one that is running produces at least its floor
dims: [snapshot, generator]
expression: dispatch >= status * min_output
ramp_up:
description: >-
one inequality for both regimes — a unit already running is held to
`ramp_limit`, a unit starting up to `start_up_limit`.
dims: [snapshot, generator]
expression: >-
dispatch - shift(dispatch, along=snapshot, offset=1, edge=0)
<= ramp_limit * previous_status + start_up_limit * (1 - previous_status)
assumptions:
output_floor_fits_under_the_cap:
holds: "min_output <= capacity"
where: "committable"
description: >-
`lower` and `upper` hold one dispatch between them, so a floor above the
cap makes a running unit infeasible rather than expensive. A unit that
cannot be switched off is held to its floor in every snapshot, so the
check is the committable ones'.
objective:
sense: minimize
expression: sum(dispatch * cost)
Unit commitment with a start-up ramp, the formulation cases: exists for. The state a unit carries into a snapshot has three regimes — a unit that is never off, the first snapshot, and every later one — and writing them at the constraint would fork ramp_up three ways. With the regimes named once, the inequality is written once.
Sets#
| Symbol | Meaning |
|---|---|
| \(\mathcal{T}\) | index \(t\) — snapshot — dispatch periods |
| \(\mathcal{G}\) | index \(g\) — generator — generating units |
Parameters#
| Symbol | Meaning |
|---|---|
| \(\mathrm{committable}\) | committable over \(\mathcal{G}\) — whether the unit may be switched off |
| \(\mathrm{status}^{\mathrm{initial}}\) | status_initial over \(\mathcal{G}\) — whether the unit was running before the horizon |
| \(\mathrm{capacity}\) | capacity over \(\mathcal{G}\) — installed capacity |
| \(\mathrm{min\_output}\) | min_output over \(\mathcal{G}\) — output floor while running |
| \(\mathrm{ramp\_limit}\) | ramp_limit over \(\mathcal{G}\) — how far output may move between snapshots while running |
| \(\mathrm{start\_up\_limit}\) | start_up_limit over \(\mathcal{G}\) — how far it may move in the snapshot it starts in |
| \(\mathrm{load}\) | load over \(\mathcal{T}\) — demand to be met |
| \(\mathrm{cost}\) | cost over \(\mathcal{G}\) — marginal cost |
Variables#
| Symbol | Meaning |
|---|---|
| \(\mathit{dispatch}\) | dispatch over \(\mathcal{T} \times \mathcal{G}\) — output of a generator in a snapshot |
| \(\mathit{status}\) | status over \(\mathcal{T} \times \mathcal{G}\) — whether the unit is running in a snapshot |
Definitions#
| Symbol | Meaning |
|---|---|
| \(\mathit{previous\_status}\) | previous_status over \(\mathcal{T} \times \mathcal{G}\) — the commitment state a unit carries into a snapshot |
Upright is what the model is given — a parameter such as \(\mathrm{committable}\), a coordinate map, a label — and italic is what the solver chooses, such as \(\mathit{dispatch}\). 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.
\(\mathrm{pos}(t)\) denotes where index \(t\) sits along its dimension's own order — the order shift steps along, not the order labels sort in — counted from \(0\). The index itself stays the coordinate, so \(t\) compares against labels and \(\mathrm{pos}(t)\) against positions.
Objective#
Subject to#
power_balance
upper
lower
ramp_up
Definitions#
previous_status
Variable domains#
dispatch
status
Assumptions#
output_floor_fits_under_the_cap
Regenerate with pixi run python -m tools.gallery.