Rung 47: the relaxed file's ramps and signs — tightening at the full build, a ramp limit per snapshot, a p_init, a unit that is not committable, and a sign¶
One rung of the PyPSA corpus: the file pypsa.yaml projected onto what this network builds, attached to that network, and held to what PyPSA solves it to.
✔ Verified against pypsa 1.3.0 — objective 12862.5 on both sides; structure ✔ 18 constraints · 5 variables, name for name; size ✔ 179 rows · ✔ 72 columns · ✔ 353 nonzeros; duals ≠ 179 rows, 1 negated,
Generator-status-p-fixed-upperoff by 560.0 — PyPSA bounds the status at 1 on the variable and writes the cap row as well, so a binding cap's dual may sit on the bound and leave the row at zero; the file states the row alone (rung 12, the linearized relaxation); model for model: 21 blocks equal, 0 documented splits, 3 recorded deviations.
Rows and columns, PyPSA against specsolve, name for name
| row | PyPSA | specsolve |
|---|---|---|
Bus-nodal_balance |
12 | 12 |
Generator-com-p-before |
3 | 3 |
Generator-com-p-current |
3 | 3 |
Generator-com-p-lower |
12 | 12 |
Generator-com-p-upper |
12 | 12 |
Generator-com-partly-shut-down |
3 | 3 |
Generator-com-partly-start-up |
3 | 3 |
Generator-com-transition-shut-down |
12 | 12 |
Generator-com-transition-start-up |
12 | 12 |
Generator-fix-p-lower |
20 | 20 |
Generator-fix-p-upper |
20 | 20 |
Generator-p-ramp_limit_down |
13 | 13 |
Generator-p-ramp_limit_up |
10 | 10 |
Generator-shut_down-p-fixed-upper |
12 | 12 |
Generator-start_up-p-fixed-upper |
12 | 12 |
Generator-status-p-fixed-upper |
12 | 12 |
Link-fix-p-lower |
4 | 4 |
Link-fix-p-upper |
4 | 4 |
| column | PyPSA | specsolve |
|---|---|---|
Generator-p |
32 | 32 |
Generator-shut_down |
12 | 12 |
Generator-start_up |
12 | 12 |
Generator-status |
12 | 12 |
Link-p |
4 | 4 |
The model¶
The same model, as math
The relaxed class of a plain n.optimize(): linearized_unit_commitment, stated on rung 1's transport surface in a file of its own. The status, its starts and its stops are shares in [0, 1] rather than binaries — a domain is the spec's, not the data's — and four rows PyPSA adds only under the keyword tighten the relaxation where a unit's start and stop cost the same. The surface is generators, links and loads with a fixed build, in one scenario, every asset active in every snapshot, and only a generator committable. examples/pypsa.yaml stays the integer one, and states the rest: a committable link or process, an extendable build, scenarios and active.
Sets¶
| Symbol | Meaning |
|---|---|
| \(\mathcal{T}\) | index \(t\) — snapshot — dispatch periods |
| \(\mathcal{N}\) | index \(n\) — bus with \(\mathrm{Generator\_bus}: \mathcal{G} \to \mathcal{N},\ \mathrm{Link\_bus0}: \mathcal{L} \to \mathcal{N},\ \mathrm{Link\_output\_bus}: \mathcal{O} \to \mathcal{N},\ \mathrm{Load\_bus}: \mathcal{D} \to \mathcal{N}\) — network nodes |
| \(\mathcal{G}\) | index \(g\) — generator with \(\mathrm{Generator\_bus}: \mathcal{G} \to \mathcal{N}\) — generating units, each on one bus |
| \(\mathcal{L}\) | index \(l\) — link with \(\mathrm{Link\_bus0}: \mathcal{L} \to \mathcal{N},\ \mathrm{Link\_output\_link}: \mathcal{O} \to \mathcal{L}\) — controllable connections, each from one bus to the buses it delivers to |
| \(\mathcal{O}\) | index \(o\) — link_output with \(\mathrm{Link\_output\_link}: \mathcal{O} \to \mathcal{L},\ \mathrm{Link\_output\_bus}: \mathcal{O} \to \mathcal{N}\) — a link's output ports, one label per port a link declares — PyPSA's bus1, bus2, … columns read long, so a link of any number of output ports is one term in the balance, data prep |
| \(\mathcal{D}\) | index \(d\) — load with \(\mathrm{Load\_bus}: \mathcal{D} \to \mathcal{N}\) — demands, each on one bus |
Parameters¶
| Symbol | Meaning |
|---|---|
| \(\mathrm{w}\) | snapshot_weightings_objective over \(\mathcal{T}\) — PyPSA's snapshot_weightings.objective — hours a snapshot stands for in the cost |
| \(\mathrm{p}^{\mathrm{nom}}\) | Generator_p_nom over \(\mathcal{G}\) — nominal power |
| \(\underline{\mathrm{p}}\) | Generator_p_min_pu over \(\mathcal{T} \times \mathcal{G}\) — least output, per unit of nominal power |
| \(\overline{\mathrm{p}}\) | Generator_p_max_pu over \(\mathcal{T} \times \mathcal{G}\) — most output, per unit of nominal power — an availability profile |
| \(\mathrm{c}\) | Generator_marginal_cost over \(\mathcal{T} \times \mathcal{G}\) — cost of one unit of output |
| \(\mathrm{sgn}\) | Generator_sign over \(\mathcal{G}\) — the sign output enters its bus's balance with — PyPSA's sign, 1 unless given, -1 for a unit that draws power |
| \(\mathrm{f}^{\mathrm{nom}}\) | Link_p_nom over \(\mathcal{L}\) — nominal power |
| \(\underline{\mathrm{f}}\) | Link_p_min_pu over \(\mathcal{T} \times \mathcal{L}\) — least flow, per unit of nominal power — negative for a link that carries both ways |
| \(\overline{\mathrm{f}}\) | Link_p_max_pu over \(\mathcal{T} \times \mathcal{L}\) — most flow, per unit of nominal power |
| \(\eta\) | Link_efficiency over \(\mathcal{T} \times \mathcal{O}\) — share of the flow that arrives at an output port, PyPSA's efficiency, efficiency2, … read long — negative where that port consumes rather than delivers |
| \(\mathrm{c}^{f}\) | Link_marginal_cost over \(\mathcal{T} \times \mathcal{L}\) — cost of one unit of flow |
| \(\mathrm{load}\) | Load_p_set over \(\mathcal{T} \times \mathcal{D}\) — demand |
| \(\mathrm{sgn}^{\mathrm{load}}\) | Load_sign over \(\mathcal{D}\) — the sign a load's demand enters its bus's balance with — PyPSA's sign, -1 unless given, 1 for a load that feeds its bus |
| \(\mathrm{com}\) | Generator_committable over \(\mathcal{G}\) — whether output is gated by an on/off status decision |
| \(\mathrm{ru}\) | Generator_ramp_limit_up over \(\mathcal{T} \times \mathcal{G}\) — most a generator may raise its output between snapshots, per unit of nominal power; no value means no limit — read at the later of the two snapshots, so the limit may change over time |
| \(\mathrm{rd}\) | Generator_ramp_limit_down over \(\mathcal{T} \times \mathcal{G}\) — most a generator may lower its output between snapshots, per unit of nominal power; no value means no limit — read at the later of the two snapshots, so the limit may change over time |
| \(\mathrm{ru}^{\mathrm{up}}\) | Generator_ramp_limit_start_up over \(\mathcal{G}\) — most output in the snapshot a unit starts, per unit of nominal power |
| \(\mathrm{rd}^{\mathrm{dn}}\) | Generator_ramp_limit_shut_down over \(\mathcal{G}\) — most output in the snapshot before a unit stops, per unit of nominal power |
| \(\mathrm{u}^{0}\) | Generator_status_initial over \(\mathcal{G}\) — one where the unit was on before the first snapshot, zero where off — PyPSA's up_time_before > 0, data prep |
| \(\mathrm{p}^{0}\) | Generator_p_init over \(\mathcal{G}\) — the output a unit brought into the horizon — PyPSA's p_init, read only where the unit came in running; no value means it is unknown, so the unit carries no ramp row at the first snapshot |
| \(\mathrm{c}^{\mathrm{up}}\) | Generator_start_up_cost over \(\mathcal{G}\) — cost of one start |
| \(\mathrm{c}^{\mathrm{dn}}\) | Generator_shut_down_cost over \(\mathcal{G}\) — cost of one stop |
| \(\mathrm{c}^{\mathrm{on}}\) | Generator_stand_by_cost over \(\mathcal{T} \times \mathcal{G}\) — cost of one snapshot spent on |
| \(\mathrm{tight}\) | Generator_partly_tightened over \(\mathcal{G}\) — whether the four tightening rows below apply — PyPSA adds them only where a unit's start-up and shut-down costs are equal; two parameters cannot be compared in a where, so the equality is data prep |
Variables¶
| Symbol | Meaning |
|---|---|
| \(p\) | Generator_p over \(\mathcal{T} \times \mathcal{G}\) — Generator-p — output of a generator in a snapshot |
| \(f\) | Link_p over \(\mathcal{T} \times \mathcal{L}\) — Link-p — PyPSA's p0, the flow measured at the Link_bus0 end: a positive value withdraws there and injects at every bus the link's output ports deliver to |
| \(u\) | Generator_status over \(\mathcal{T} \times \mathcal{G}\) — Generator-status — how much of a committable unit is on, a share in [0, 1] rather than a binary: the relaxation linearized_unit_commitment solves |
| \(\mathit{up}\) | Generator_start_up over \(\mathcal{T} \times \mathcal{G}\) — Generator-start_up — how much of a committable unit turns on this snapshot |
| \(\mathit{dn}\) | Generator_shut_down over \(\mathcal{T} \times \mathcal{G}\) — Generator-shut_down — how much of a committable unit turns off this snapshot |
Definitions¶
| Symbol | Meaning |
|---|---|
| \(\mathit{Generator\_previous\_status}\) | Generator_previous_status over \(\mathcal{T} \times \mathcal{G}\) — the commitment state a generator carries into a snapshot — the state it brought into the horizon at the first, the previous snapshot's after that |
| \(\mathit{Generator\_previous\_p}\) | Generator_previous_p over \(\mathcal{T} \times \mathcal{G}\) — the output a generator carries into a snapshot — at the first, the p_init it brought in where it came in running and nothing where it came in off; the previous snapshot's after that |
| \(\mathrm{Generator\_ramp\_up\_rate}\) | Generator_ramp_up_rate over \(\mathcal{T} \times \mathcal{G}\) — the ramp limit a unit's up row reads — PyPSA's ramp_limit_up, or the full build where it has none, since a start-up ramp alone builds the row |
| \(\mathrm{Generator\_ramp\_down\_rate}\) | Generator_ramp_down_rate over \(\mathcal{T} \times \mathcal{G}\) — the ramp limit a unit's down row reads — PyPSA's ramp_limit_down, or the full build where it has none, since a shut-down ramp alone builds the row |
| \(\mathrm{Generator\_start\_up\_rate}\) | Generator_start_up_rate over \(\mathcal{G}\) — the start-up ramp a unit's up row reads — PyPSA's ramp_limit_start_up, or the full build where it has none |
| \(\mathrm{Generator\_shut\_down\_rate}\) | Generator_shut_down_rate over \(\mathcal{G}\) — the shut-down ramp a unit's down row reads — PyPSA's ramp_limit_shut_down, or the full build where it has none |
| \(\mathit{Generator\_ramp\_up\_allowance}\) | Generator_ramp_up_allowance over \(\mathcal{T} \times \mathcal{G}\) — how far a generator may raise output between two snapshots — its ramp limit of the build while it stays on, plus its start-up ramp in the snapshot it turns on |
| \(\mathit{Generator\_ramp\_down\_allowance}\) | Generator_ramp_down_allowance over \(\mathcal{T} \times \mathcal{G}\) — how far a generator may lower output between two snapshots — its ramp limit of the build while it stays on, plus its shut-down ramp in the snapshot it turns off |
\(\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¶
Generator_fix_p_lower
Generator_fix_p_upper
Link_fix_p_lower
Link_fix_p_upper
Bus_nodal_balance
Generator_com_p_lower
Generator_com_p_upper
Generator_com_transition_start_up
Generator_com_transition_shut_down
Generator_p_ramp_limit_up
Generator_p_ramp_limit_down
Generator_status_p_fixed_upper
Generator_start_up_p_fixed_upper
Generator_shut_down_p_fixed_upper
Generator_com_p_before
Generator_com_p_current
Generator_com_partly_start_up
Generator_com_partly_shut_down
Definitions¶
Generator_previous_status
Generator_previous_p
Generator_ramp_up_rate
Generator_ramp_down_rate
Generator_start_up_rate
Generator_shut_down_rate
Generator_ramp_up_allowance
Generator_ramp_down_allowance
Variable domains¶
Generator_p
Link_p
Generator_status
Generator_start_up
Generator_shut_down
The spec, differential/pypsa/rungs/rung_47_linearized_ramps.yaml — the file projected onto what this rung builds:
description: 'The relaxed class of a plain `n.optimize()`: `linearized_unit_commitment`, stated on rung
1''s transport surface in a file of its own. The status, its starts and its stops are shares in [0,
1] rather than binaries — a domain is the spec''s, not the data''s — and four rows PyPSA adds only under
the keyword tighten the relaxation where a unit''s start and stop cost the same. The surface is generators,
links and loads with a fixed build, in one scenario, every asset active in every snapshot, and only
a generator committable. `examples/pypsa.yaml` stays the integer one, and states the rest: a committable
link or process, an extendable build, scenarios and `active`.'
dimensions:
snapshot: {description: dispatch periods, dtype: datetime}
bus: {description: network nodes}
generator: {description: 'generating units, each on one bus'}
link: {description: 'controllable connections, each from one bus to the buses it delivers to'}
link_output: {description: 'a link''s output ports, one label per port a link declares — PyPSA''s `bus1`,
`bus2`, … columns read long, so a link of any number of output ports is one term in the balance,
data prep'}
load: {description: 'demands, each on one bus'}
relations:
Generator_bus: {description: the bus a generator sits on, key: generator, values: bus}
Link_bus0: {description: the bus a link leaves, key: link, values: bus}
Link_output_link: {description: the link an output port belongs to, key: link_output, values: link}
Link_output_bus: {description: 'the bus an output port delivers to — PyPSA''s `bus1`, `bus2`, … columns.
A link of three output ports is three labels here rather than a third relation, so the file states
any number of them', key: link_output, values: bus}
Load_bus: {description: the bus a load sits on, key: load, values: bus}
parameters:
snapshot_weightings_objective:
description: PyPSA's `snapshot_weightings.objective` — hours a snapshot stands for in the cost
dims: [snapshot]
Generator_p_nom:
description: nominal power
dims: [generator]
Generator_p_min_pu:
description: least output, per unit of nominal power
dims: [snapshot, generator]
Generator_p_max_pu:
description: most output, per unit of nominal power — an availability profile
dims: [snapshot, generator]
Generator_marginal_cost:
description: cost of one unit of output
dims: [snapshot, generator]
Generator_sign:
description: the sign output enters its bus's balance with — PyPSA's `sign`, `1` unless given, `-1`
for a unit that draws power
dims: [generator]
Link_p_nom:
description: nominal power
dims: [link]
Link_p_min_pu:
description: least flow, per unit of nominal power — negative for a link that carries both ways
dims: [snapshot, link]
Link_p_max_pu:
description: most flow, per unit of nominal power
dims: [snapshot, link]
Link_efficiency:
description: share of the flow that arrives at an output port, PyPSA's `efficiency`, `efficiency2`,
… read long — negative where that port consumes rather than delivers
dims: [snapshot, link_output]
Link_marginal_cost:
description: cost of one unit of flow
dims: [snapshot, link]
Load_p_set:
description: demand
dims: [snapshot, load]
Load_sign:
description: the sign a load's demand enters its bus's balance with — PyPSA's `sign`, `-1` unless
given, `1` for a load that feeds its bus
dims: [load]
Generator_committable:
description: whether output is gated by an on/off status decision
dims: [generator]
dtype: bool
Generator_ramp_limit_up:
description: most a generator may raise its output between snapshots, per unit of nominal power; no
value means no limit — read at the later of the two snapshots, so the limit may change over time
dims: [snapshot, generator]
Generator_ramp_limit_down:
description: most a generator may lower its output between snapshots, per unit of nominal power; no
value means no limit — read at the later of the two snapshots, so the limit may change over time
dims: [snapshot, generator]
Generator_ramp_limit_start_up:
description: most output in the snapshot a unit starts, per unit of nominal power
dims: [generator]
Generator_ramp_limit_shut_down:
description: most output in the snapshot before a unit stops, per unit of nominal power
dims: [generator]
Generator_status_initial:
description: one where the unit was on before the first snapshot, zero where off — PyPSA's `up_time_before
> 0`, data prep
dims: [generator]
dtype: int
Generator_p_init:
description: the output a unit brought into the horizon — PyPSA's `p_init`, read only where the unit
came in running; no value means it is unknown, so the unit carries no ramp row at the first snapshot
dims: [generator]
Generator_start_up_cost:
description: cost of one start
dims: [generator]
Generator_shut_down_cost:
description: cost of one stop
dims: [generator]
Generator_stand_by_cost:
description: cost of one snapshot spent on
dims: [snapshot, generator]
Generator_partly_tightened:
description: whether the four tightening rows below apply — PyPSA adds them only where a unit's start-up
and shut-down costs are equal; two parameters cannot be compared in a `where`, so the equality is
data prep
dims: [generator]
dtype: bool
variables:
Generator_p:
description: '`Generator-p` — output of a generator in a snapshot'
dims: [snapshot, generator]
Link_p:
description: '`Link-p` — PyPSA''s `p0`, the flow measured at the `Link_bus0` end: a positive value
withdraws there and injects at every bus the link''s output ports deliver to'
dims: [snapshot, link]
Generator_status:
description: '`Generator-status` — how much of a committable unit is on, a share in [0, 1] rather
than a binary: the relaxation `linearized_unit_commitment` solves'
dims: [snapshot, generator]
where: Generator_committable
bounds: {lower: 0}
Generator_start_up:
description: '`Generator-start_up` — how much of a committable unit turns on this snapshot'
dims: [snapshot, generator]
where: Generator_committable
bounds: {lower: 0}
Generator_shut_down:
description: '`Generator-shut_down` — how much of a committable unit turns off this snapshot'
dims: [snapshot, generator]
where: Generator_committable
bounds: {lower: 0}
constraints:
Generator_fix_p_lower:
description: '`Generator-fix-p-lower` — a generator outputs at least its minimum'
dims: [snapshot, generator]
where: not Generator_committable
expression: Generator_p >= Generator_p_min_pu * Generator_p_nom
Generator_fix_p_upper:
description: '`Generator-fix-p-upper` — a generator outputs at most what is available'
dims: [snapshot, generator]
where: not Generator_committable
expression: Generator_p <= Generator_p_max_pu * Generator_p_nom
Link_fix_p_lower:
description: '`Link-fix-p-lower` — a link carries at least its minimum, negative for the other way'
dims: [snapshot, link]
expression: Link_p >= Link_p_min_pu * Link_p_nom
Link_fix_p_upper:
description: '`Link-fix-p-upper` — a link carries at most its nominal power'
dims: [snapshot, link]
expression: Link_p <= Link_p_max_pu * Link_p_nom
Bus_nodal_balance:
description: '`Bus-nodal_balance` — what is generated at a bus, less what the links take away, plus
what arrives over them after losses, meets the load there. Each generator and load term enters with
its component''s `sign` (`constraints.py:1428-1429`, `:1538`)'
dims: [snapshot, bus]
expression: sum(Generator_sign * Generator_p, by=Generator_bus, over=generator, into=bus) - sum(Link_p,
by=Link_bus0, over=link, into=bus) + sum(at(Link_p, by=Link_output_link, over=link, into=link_output)
* Link_efficiency, by=Link_output_bus, over=link_output, into=bus) == -sum(Load_sign * Load_p_set,
by=Load_bus, over=load, into=bus)
Generator_com_p_lower:
description: '`Generator-com-p-lower` — a committed unit outputs at least its minimum; off, at least
nothing'
dims: [snapshot, generator]
where: Generator_committable
expression: Generator_p >= (Generator_p_min_pu * Generator_p_nom) * Generator_status
Generator_com_p_upper:
description: '`Generator-com-p-upper` — a committed unit outputs at most what is available; off, at
most nothing'
dims: [snapshot, generator]
where: Generator_committable
expression: Generator_p <= (Generator_p_max_pu * Generator_p_nom) * Generator_status
Generator_com_transition_start_up:
description: '`Generator-com-transition-start-up` — turning on is a start, counted against the state
the unit carried into the snapshot'
dims: [snapshot, generator]
where: Generator_committable
expression: Generator_start_up >= Generator_status - Generator_previous_status
Generator_com_transition_shut_down:
description: '`Generator-com-transition-shut-down` — turning off is a stop, counted against the state
the unit carried into the snapshot'
dims: [snapshot, generator]
where: Generator_committable
expression: Generator_shut_down >= Generator_previous_status - Generator_status
Generator_p_ramp_limit_up:
description: '`Generator-p-ramp_limit_up` — a generator raises output no faster than its ramp limit
of the build, and a committed one no further than its start-up ramp in the snapshot it turns on.
A unit that came into the horizon running carries a row at the first snapshot only where its `p_init`
gives the output it brought in'
dims: [snapshot, generator]
where: (Generator_ramp_limit_up OR Generator_ramp_limit_start_up) AND (position(snapshot) > 0 OR Generator_status_initial
== 0 OR Generator_p_init)
expression: Generator_p - Generator_previous_p <= Generator_ramp_up_allowance
Generator_p_ramp_limit_down:
description: '`Generator-p-ramp_limit_down` — a generator lowers output no faster than its ramp limit
of the build, and a committed one no further than its shut-down ramp in the snapshot it turns off.
A unit that came into the horizon running carries a row at the first snapshot only where its `p_init`
gives the output it brought in'
dims: [snapshot, generator]
where: (Generator_ramp_limit_down OR Generator_ramp_limit_shut_down) AND (position(snapshot) > 0 OR
Generator_status_initial == 0 OR Generator_p_init)
expression: Generator_previous_p - Generator_p <= Generator_ramp_down_allowance
Generator_status_p_fixed_upper:
description: '`Generator-status-p-fixed-upper` — a status is at most one, an explicit row as PyPSA
writes it'
dims: [snapshot, generator]
where: Generator_committable
expression: Generator_status <= 1
Generator_start_up_p_fixed_upper:
description: '`Generator-start_up-p-fixed-upper` — a start is at most one, an explicit row as PyPSA
writes it'
dims: [snapshot, generator]
where: Generator_committable
expression: Generator_start_up <= 1
Generator_shut_down_p_fixed_upper:
description: '`Generator-shut_down-p-fixed-upper` — a stop is at most one, an explicit row as PyPSA
writes it'
dims: [snapshot, generator]
where: Generator_committable
expression: Generator_shut_down <= 1
Generator_com_p_before:
description: '`Generator-com-p-before` — the output a unit had entering this snapshot fits the share
of it still on, less the share it is shutting down at the shut-down ramp. The translated term vacates
the first snapshot, as PyPSA''s `sns[1:]` does'
dims: [snapshot, generator]
where: Generator_committable AND Generator_partly_tightened
expression: shift(Generator_p, along=snapshot, offset=1) - Generator_shut_down_rate * Generator_p_nom
* shift(Generator_status, along=snapshot, offset=1) - (Generator_p_max_pu * Generator_p_nom - Generator_shut_down_rate
* Generator_p_nom) * (Generator_status - Generator_start_up) <= 0
Generator_com_p_current:
description: '`Generator-com-p-current` — output fits the share on, and the share starting up only
up to the start-up ramp'
dims: [snapshot, generator]
where: Generator_committable AND Generator_partly_tightened AND position(snapshot) > 0
expression: Generator_p - Generator_p_max_pu * Generator_p_nom * Generator_status + (Generator_p_max_pu
* Generator_p_nom - Generator_start_up_rate * Generator_p_nom) * Generator_start_up <= 0
Generator_com_partly_start_up:
description: '`Generator-com-partly-start-up` — raising output while a share is starting up is bounded
by the ramp of the share on and the start-up ramp of the share coming on'
dims: [snapshot, generator]
where: Generator_committable AND Generator_partly_tightened
expression: Generator_p - shift(Generator_p, along=snapshot, offset=1) - (Generator_p_min_pu * Generator_p_nom
+ Generator_ramp_up_rate * Generator_p_nom) * Generator_status + Generator_p_min_pu * Generator_p_nom
* shift(Generator_status, along=snapshot, offset=1) + (Generator_p_min_pu * Generator_p_nom + Generator_ramp_up_rate
* Generator_p_nom - Generator_start_up_rate * Generator_p_nom) * Generator_start_up <= 0
Generator_com_partly_shut_down:
description: '`Generator-com-partly-shut-down` — lowering output while a share is shutting down is
bounded likewise, by the shut-down ramp'
dims: [snapshot, generator]
where: Generator_committable AND Generator_partly_tightened
expression: shift(Generator_p, along=snapshot, offset=1) - Generator_p - Generator_shut_down_rate
* Generator_p_nom * shift(Generator_status, along=snapshot, offset=1) + (Generator_shut_down_rate
* Generator_p_nom - Generator_ramp_down_rate * Generator_p_nom) * Generator_status - (Generator_p_min_pu
* Generator_p_nom + Generator_ramp_down_rate * Generator_p_nom - Generator_shut_down_rate * Generator_p_nom)
* Generator_start_up <= 0
expressions:
Generator_previous_status:
description: the commitment state a generator carries into a snapshot — the state it brought into
the horizon at the first, the previous snapshot's after that
dims: [snapshot, generator]
cases:
opening: {when: position(snapshot) == 0, expression: Generator_status_initial}
otherwise: shift(Generator_status, along=snapshot, offset=1)
Generator_previous_p:
description: the output a generator carries into a snapshot — at the first, the `p_init` it brought
in where it came in running and nothing where it came in off; the previous snapshot's after that
dims: [snapshot, generator]
cases:
opening: {when: position(snapshot) == 0, expression: Generator_status_initial * Generator_p_init}
otherwise: shift(Generator_p, along=snapshot, offset=1)
Generator_ramp_up_rate:
description: the ramp limit a unit's up row reads — PyPSA's `ramp_limit_up`, or the full build where
it has none, since a start-up ramp alone builds the row
dims: [snapshot, generator]
cases:
given: {when: Generator_ramp_limit_up, expression: Generator_ramp_limit_up}
otherwise: 1
Generator_ramp_down_rate:
description: the ramp limit a unit's down row reads — PyPSA's `ramp_limit_down`, or the full build
where it has none, since a shut-down ramp alone builds the row
dims: [snapshot, generator]
cases:
given: {when: Generator_ramp_limit_down, expression: Generator_ramp_limit_down}
otherwise: 1
Generator_start_up_rate:
description: the start-up ramp a unit's up row reads — PyPSA's `ramp_limit_start_up`, or the full
build where it has none
dims: [generator]
cases:
given: {when: Generator_ramp_limit_start_up, expression: Generator_ramp_limit_start_up}
otherwise: 1
Generator_shut_down_rate:
description: the shut-down ramp a unit's down row reads — PyPSA's `ramp_limit_shut_down`, or the full
build where it has none
dims: [generator]
cases:
given: {when: Generator_ramp_limit_shut_down, expression: Generator_ramp_limit_shut_down}
otherwise: 1
Generator_ramp_up_allowance:
description: how far a generator may raise output between two snapshots — its ramp limit of the build
while it stays on, plus its start-up ramp in the snapshot it turns on
dims: [snapshot, generator]
cases:
committed: {when: Generator_committable, expression: Generator_ramp_up_rate * Generator_p_nom *
Generator_previous_status + Generator_start_up_rate * Generator_p_nom * (Generator_status -
Generator_previous_status)}
otherwise: Generator_ramp_up_rate * Generator_p_nom
Generator_ramp_down_allowance:
description: how far a generator may lower output between two snapshots — its ramp limit of the build
while it stays on, plus its shut-down ramp in the snapshot it turns off
dims: [snapshot, generator]
cases:
committed: {when: Generator_committable, expression: Generator_ramp_down_rate * Generator_p_nom
* Generator_status + Generator_shut_down_rate * Generator_p_nom * (Generator_previous_status
- Generator_status)}
otherwise: Generator_ramp_down_rate * Generator_p_nom
objective: {sense: minimize, description: 'operating cost by weighted snapshot, plus what starts, stops
and standing by cost', expression: sum(Generator_p * Generator_marginal_cost * snapshot_weightings_objective)
+ sum(Link_p * Link_marginal_cost * snapshot_weightings_objective) + sum(Generator_status * Generator_stand_by_cost
* snapshot_weightings_objective) + sum(Generator_start_up * Generator_start_up_cost) + sum(Generator_shut_down
* Generator_shut_down_cost)}
The prep — every table the spec declares, from the network — and the solve:
from differential.pypsa.prep import relation, static, varying, weighting
n = build() # the network from the PyPSA tab
sources = {
'snapshot': pl.Series('snapshot', list(timesteps(n)), dtype=pl.Datetime('us')),
'bus': pl.Series('bus', list(names(n.buses.index).astype(str)), dtype=pl.String),
**{
dim: pl.Series(dim, list(names(n.static(component).index).astype(str)), dtype=pl.String)
for component, dim in DIM.items()
},
**scenarios(n),
**periods(n),
**carriers(n, multi),
'Generator_bus': relation(n, 'Generator', 'bus'),
'Link_bus0': relation(n, 'Link', 'bus0'),
'Load_bus': relation(n, 'Load', 'bus'),
'snapshot_weightings_objective': weighting(n, 'objective'),
'Generator_sign': per_component('Generator', first_scenario(n.generators['sign'])),
'Load_p_set': varying(n, 'Load', 'p_set'),
'Load_sign': per_component('Load', first_scenario(loads['sign'])),
}
with sps.solve('differential/pypsa/rungs/rung_47_linearized_ramps.yaml', sources) as solution:
solution.objective # 12862.5
The network, rung_47_linearized_ramps.py in the corpus — the spine plus what this rung adds:
# SPDX-FileCopyrightText: mathspec Contributors
#
# SPDX-License-Identifier: MIT
"""Rung 47: the relaxed file's ramps and signs — tightening at the full build, a ramp limit per snapshot, a `p_init`, a unit that is not committable, and a sign."""
from __future__ import annotations
import math
import spine
MODEL = 'pypsa_linearized_uc.yaml'
OPTIMIZE = {'linearized_unit_commitment': True}
def build():
"""The spine plus a relax bus: five units that each carry one of the rows under review, a feeding load and a dear backup."""
n = spine.build()
n.add('Bus', 'relax')
n.add(
'Generator',
'tight47',
bus='relax',
committable=True,
p_nom=50,
p_min_pu=0.2,
marginal_cost=2,
up_time_before=0,
ramp_limit_start_up=0.5,
ramp_limit_shut_down=0.5,
start_up_cost=10,
shut_down_cost=10,
)
n.add(
'Generator',
'steep47',
bus='relax',
committable=True,
p_nom=40,
marginal_cost=3,
start_up_cost=1,
ramp_limit_up=[0.25, 0.25, math.nan, 0.25],
ramp_limit_down=[0.25, 0.25, 0.25, math.nan],
ramp_limit_start_up=0.25,
)
n.add(
'Generator',
'warm47',
bus='relax',
committable=True,
p_nom=40,
marginal_cost=50,
start_up_cost=1,
p_init=40,
ramp_limit_down=0.25,
ramp_limit_shut_down=0.5,
)
n.add('Generator', 'fixed47', bus='relax', p_nom=60, marginal_cost=1, ramp_limit_up=0.25, ramp_limit_down=0.25)
n.add('Generator', 'sink47', bus='relax', p_nom=20, p_min_pu=0.5, sign=-1)
n.add('Generator', 'backup47', bus='relax', p_nom=300, marginal_cost=500)
n.add('Load', 'feed47', bus='relax', p_set=10, sign=1)
n.add('Load', 'swing47', bus='relax', p_set=[60, 60, 140, 40])
return n
The data¶
Every table this spec declares was first declared by a lower rung; its values here are in the prep above.