Skip to content

Rung 44: linearized commitment with the rows the integer file has — a unit that must stay down, ramps read at the full build where a limit is missing, and maintenance

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 7400.0 on both sides; structure ✔ 21 constraints · 8 variables, name for name; size ✔ 191 rows · ✔ 100 columns · ✔ 378 nonzeros; duals — integer model, no duals; model for model: 27 blocks equal, 0 documented splits, 3 recorded deviations.

Rows and columns, PyPSA against specsolve, name for name
row PyPSA specsolve
Bus-nodal_balance 8 8
Generator-com-down-time 3 3
Generator-com-p-lower 16 16
Generator-com-p-upper 16 16
Generator-com-status-min_down_time_must_stay_up 2 2
Generator-com-transition-shut-down 16 16
Generator-com-transition-start-up 16 16
Generator-fix-p-lower 12 12
Generator-fix-p-upper 12 12
Generator-maint-event-count 2 2
Generator-maint-status-lb 4 4
Generator-maint-status-le-maint 4 4
Generator-maint-status-le-status 4 4
Generator-maint-window 8 8
Generator-p-ramp_limit_down 4 4
Generator-p-ramp_limit_up 8 8
Generator-shut_down-p-fixed-upper 16 16
Generator-start_up-p-fixed-upper 16 16
Generator-status-p-fixed-upper 16 16
Link-fix-p-lower 4 4
Link-fix-p-upper 4 4
column PyPSA specsolve
Generator-maintenance 8 8
Generator-maintenance_start 8 8
Generator-maintenance_status 4 4
Generator-p 28 28
Generator-shut_down 16 16
Generator-start_up 16 16
Generator-status 16 16
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 with \(\mathrm{Generator\_maintenance\_cover} \subseteq \mathcal{G} \times \mathcal{T} \times \mathcal{T}\) — 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},\ \mathrm{Generator\_maintenance\_cover} \subseteq \mathcal{G} \times \mathcal{T} \times \mathcal{T}\) — 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{DT}\) Generator_min_down_time over \(\mathcal{G}\) — least snapshots a unit stays off once stopped
\(\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{rest}\) Generator_must_stay_down over \(\mathcal{T} \times \mathcal{G}\) — true while the down time a unit brought into the horizon still binds — PyPSA's min_down_time - down_time_before snapshots, where down_time_before > 0, data prep for the same reason
\(\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{mnt}\) Generator_maintainable over \(\mathcal{G}\) — whether a generator must be taken off for maintenance within the horizon
\(\gamma\) Generator_maintenance_pu over \(\mathcal{G}\) — the share of the build a maintenance event takes off
\(\mathrm{n}^{\mathrm{mnt}}\) Generator_maintenance_events over \(\mathcal{G}\) — how many maintenance events the horizon holds

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
\(\mu\) Generator_maintenance over \(\mathcal{T} \times \mathcal{G}\) — Generator-maintenance — whether a maintainable generator is in maintenance: continuous, and one exactly where an event covers the snapshot
\(\mu^{\mathrm{up}}\) Generator_maintenance_start over \(\mathcal{T} \times \mathcal{G}\) — Generator-maintenance_start — whether a maintenance event starts in this snapshot — a binary, which the keyword does not relax
\(\mu^{u}\) Generator_maintenance_status over \(\mathcal{T} \times \mathcal{G}\) — Generator-maintenance_status — the status while in maintenance, zero otherwise: the product the maint-status rows linearize, so a unit in maintenance may also be off

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
\(\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{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

\(\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

\[ \min \sum_{t \in \mathcal{T},\ g \in \mathcal{G}} p_{t,g} \cdot \mathrm{c}_{t,g} \cdot \mathrm{w}_{t} + \sum_{t \in \mathcal{T},\ l \in \mathcal{L}} f_{t,l} \cdot \mathrm{c}^{f}_{t,l} \cdot \mathrm{w}_{t} + \sum_{t \in \mathcal{T},\ g \in \mathcal{G}} u_{t,g} \cdot \mathrm{c}^{\mathrm{on}}_{t,g} \cdot \mathrm{w}_{t} + \sum_{t \in \mathcal{T},\ g \in \mathcal{G}} \mathit{up}_{t,g} \cdot \mathrm{c}^{\mathrm{up}}_{g} + \sum_{t \in \mathcal{T},\ g \in \mathcal{G}} \mathit{dn}_{t,g} \cdot \mathrm{c}^{\mathrm{dn}}_{g} \]

Subject to

Generator_fix_p_lower

\[ p_{t,g} \ge \underline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \cdot \left( 1 - \gamma_{g} \cdot \mu_{t,g} \right) \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \neg \mathrm{com}_{g} \]

Generator_fix_p_upper

\[ p_{t,g} \le \overline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \cdot \left( 1 - \gamma_{g} \cdot \mu_{t,g} \right) \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \neg \mathrm{com}_{g} \]

Link_fix_p_lower

\[ f_{t,l} \ge \underline{\mathrm{f}}_{t,l} \cdot \mathrm{f}^{\mathrm{nom}}_{l} \qquad \forall\, t \in \mathcal{T},\ l \in \mathcal{L} \]

Link_fix_p_upper

\[ f_{t,l} \le \overline{\mathrm{f}}_{t,l} \cdot \mathrm{f}^{\mathrm{nom}}_{l} \qquad \forall\, t \in \mathcal{T},\ l \in \mathcal{L} \]

Bus_nodal_balance

\[ \sum_{g \in \mathcal{G} \,:\, \mathrm{Generator\_bus}(g) = n} \mathrm{sgn}_{g} \cdot p_{t,g} - \left( \sum_{l \in \mathcal{L} \,:\, \mathrm{Link\_bus0}(l) = n} f_{t,l} \right) + \sum_{o \in \mathcal{O} \,:\, \mathrm{Link\_output\_bus}(o) = n} f_{t,\mathrm{Link\_output\_link}(o)} \cdot \eta_{t,o} = -\left( \sum_{d \in \mathcal{D} \,:\, \mathrm{Load\_bus}(d) = n} \mathrm{sgn}^{\mathrm{load}}_{d} \cdot \mathrm{load}_{t,d} \right) \qquad \forall\, t \in \mathcal{T},\ n \in \mathcal{N} \]

Generator_com_p_lower

\[ p_{t,g} \ge \underline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \cdot \left( u_{t,g} - \gamma_{g} \cdot \mu^{u}_{t,g} \right) \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \]

Generator_com_p_upper

\[ p_{t,g} \le \overline{\mathrm{p}}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \cdot \left( u_{t,g} - \gamma_{g} \cdot \mu^{u}_{t,g} \right) \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \]

Generator_com_transition_start_up

\[ \mathit{up}_{t,g} \ge u_{t,g} - \mathit{Generator\_previous\_status}_{t,g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \]

Generator_com_transition_shut_down

\[ \mathit{dn}_{t,g} \ge \mathit{Generator\_previous\_status}_{t,g} - u_{t,g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \]

Generator_com_down_time

\[ \sum_{t' \in \mathcal{T} \,:\, 0 \le t - t' < \mathrm{DT}} \mathit{dn}_{t',g} \le 1 - u_{t,g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{DT}_{g} > 0 \wedge \mathrm{pos}(t) > 0 \]

Generator_com_status_must_stay_down

\[ u_{t,g} = 0 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \wedge \mathrm{rest}_{t,g} \]

Generator_p_ramp_limit_up

\[ p_{t,g} - \mathit{Generator\_previous\_p}_{t,g} \le \mathit{Generator\_ramp\_up\_allowance}_{t,g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \left( \mathrm{ru}_{t,g} \text{ is defined} \vee \mathrm{ru}^{\mathrm{up}}_{g} \text{ is defined} \right) \wedge \left( \mathrm{pos}(t) > 0 \vee \mathrm{u}^{0}_{g} = 0 \vee \mathrm{p}^{0}_{g} \text{ is defined} \right) \]

Generator_p_ramp_limit_down

\[ \mathit{Generator\_previous\_p}_{t,g} - p_{t,g} \le \mathit{Generator\_ramp\_down\_allowance}_{t,g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \left( \mathrm{rd}_{t,g} \text{ is defined} \vee \mathrm{rd}^{\mathrm{dn}}_{g} \text{ is defined} \right) \wedge \left( \mathrm{pos}(t) > 0 \vee \mathrm{u}^{0}_{g} = 0 \vee \mathrm{p}^{0}_{g} \text{ is defined} \right) \]

Generator_status_p_fixed_upper

\[ u_{t,g} \le 1 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \]

Generator_start_up_p_fixed_upper

\[ \mathit{up}_{t,g} \le 1 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \]

Generator_shut_down_p_fixed_upper

\[ \mathit{dn}_{t,g} \le 1 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \]

Generator_maint_event_count

\[ \sum_{t \in \mathcal{T}} \mu^{\mathrm{up}}_{t,g} = \mathrm{n}^{\mathrm{mnt}}_{g} \qquad \forall\, g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \]

Generator_maint_window

\[ \mu_{t,g} = \sum_{t' \in \mathcal{T} \,:\, \left( g,\ t',\ t \right) \in \mathrm{Generator\_maintenance\_cover}} \mu^{\mathrm{up}}_{t',g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \]

Generator_maint_status_le_status

\[ \mu^{u}_{t,g} \le u_{t,g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{com}_{g} \]

Generator_maint_status_le_maint

\[ \mu^{u}_{t,g} \le \mu_{t,g} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{com}_{g} \]

Generator_maint_status_lb

\[ \mu^{u}_{t,g} \ge u_{t,g} + \mu_{t,g} - 1 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{com}_{g} \]

Definitions

Generator_previous_status

\[ \mathit{Generator\_previous\_status}_{t,g} = \begin{cases} \mathrm{u}^{0}_{g} & \text{if } \mathrm{pos}(t) = 0 \\ u_{t - 1,g} & \text{otherwise} \end{cases} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

Generator_previous_p

\[ \mathit{Generator\_previous\_p}_{t,g} = \begin{cases} \mathrm{u}^{0}_{g} \cdot \mathrm{p}^{0}_{g} & \text{if } \mathrm{pos}(t) = 0 \\ p_{t - 1,g} & \text{otherwise} \end{cases} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

Generator_ramp_up_allowance

\[ \mathit{Generator\_ramp\_up\_allowance}_{t,g} = \begin{cases} \mathrm{Generator\_ramp\_up\_rate}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \cdot \mathit{Generator\_previous\_status}_{t,g} + \mathrm{Generator\_start\_up\_rate}_{g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \cdot \left( u_{t,g} - \mathit{Generator\_previous\_status}_{t,g} \right) & \text{if } \mathrm{com}_{g} \\ \mathrm{Generator\_ramp\_up\_rate}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} & \text{otherwise} \end{cases} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

Generator_ramp_down_allowance

\[ \mathit{Generator\_ramp\_down\_allowance}_{t,g} = \begin{cases} \mathrm{Generator\_ramp\_down\_rate}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \cdot u_{t,g} + \mathrm{Generator\_shut\_down\_rate}_{g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} \cdot \left( \mathit{Generator\_previous\_status}_{t,g} - u_{t,g} \right) & \text{if } \mathrm{com}_{g} \\ \mathrm{Generator\_ramp\_down\_rate}_{t,g} \cdot \mathrm{p}^{\mathrm{nom}}_{g} & \text{otherwise} \end{cases} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

Generator_ramp_up_rate

\[ \mathrm{Generator\_ramp\_up\_rate}_{t,g} = \begin{cases} \mathrm{ru}_{t,g} & \text{if } \mathrm{ru}_{t,g} \text{ is defined} \\ 1 & \text{otherwise} \end{cases} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

Generator_ramp_down_rate

\[ \mathrm{Generator\_ramp\_down\_rate}_{t,g} = \begin{cases} \mathrm{rd}_{t,g} & \text{if } \mathrm{rd}_{t,g} \text{ is defined} \\ 1 & \text{otherwise} \end{cases} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

Generator_start_up_rate

\[ \mathrm{Generator\_start\_up\_rate}_{g} = \begin{cases} \mathrm{ru}^{\mathrm{up}}_{g} & \text{if } \mathrm{ru}^{\mathrm{up}}_{g} \text{ is defined} \\ 1 & \text{otherwise} \end{cases} \qquad \forall\, g \in \mathcal{G} \]

Generator_shut_down_rate

\[ \mathrm{Generator\_shut\_down\_rate}_{g} = \begin{cases} \mathrm{rd}^{\mathrm{dn}}_{g} & \text{if } \mathrm{rd}^{\mathrm{dn}}_{g} \text{ is defined} \\ 1 & \text{otherwise} \end{cases} \qquad \forall\, g \in \mathcal{G} \]

Variable domains

Generator_p

\[ p_{t,g} \in \mathbb{R} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \]

Link_p

\[ f_{t,l} \in \mathbb{R} \qquad \forall\, t \in \mathcal{T},\ l \in \mathcal{L} \]

Generator_status

\[ u_{t,g} \ge 0 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \]

Generator_start_up

\[ \mathit{up}_{t,g} \ge 0 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \]

Generator_shut_down

\[ \mathit{dn}_{t,g} \ge 0 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{com}_{g} \]

Generator_maintenance

\[ 0 \le \mu_{t,g} \le 1 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \]

Generator_maintenance_start

\[ \mu^{\mathrm{up}}_{t,g} \in \{0, 1\} \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \]

Generator_maintenance_status

\[ \mu^{u}_{t,g} \ge 0 \qquad \forall\, t \in \mathcal{T},\ g \in \mathcal{G} \,:\, \mathrm{mnt}_{g} \wedge \mathrm{com}_{g} \]

The spec, differential/pypsa/rungs/rung_44_linearized_commitment.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}
  Generator_maintenance_cover:
    description: the snapshots a maintenance event covers, by the snapshot it starts in — the start and
      the snapshots after it, until their generator weightings reach `maintenance_duration`, data prep;
      no rows for a generator that is not maintainable
    key: {generator: generator, start: snapshot, covered: snapshot}
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_min_down_time:
    description: least snapshots a unit stays off once stopped
    dims: [generator]
    dtype: int
  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_must_stay_down:
    description: true while the down time a unit brought into the horizon still binds — PyPSA's `min_down_time
      - down_time_before` snapshots, where `down_time_before > 0`, data prep for the same reason
    dims: [snapshot, generator]
    dtype: bool
  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_maintainable:
    description: whether a generator must be taken off for maintenance within the horizon
    dims: [generator]
    dtype: bool
  Generator_maintenance_pu:
    description: the share of the build a maintenance event takes off
    dims: [generator]
  Generator_maintenance_events:
    description: how many maintenance events the horizon holds
    dims: [generator]
    dtype: int
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}
  Generator_maintenance:
    description: '`Generator-maintenance` — whether a maintainable generator is in maintenance: continuous,
      and one exactly where an event covers the snapshot'
    dims: [snapshot, generator]
    where: Generator_maintainable
    absence: zero
    bounds: {lower: 0, upper: 1}
  Generator_maintenance_start:
    description: '`Generator-maintenance_start` — whether a maintenance event starts in this snapshot
      — a binary, which the keyword does not relax'
    dims: [snapshot, generator]
    where: Generator_maintainable
    absence: zero
    domain: binary
  Generator_maintenance_status:
    description: '`Generator-maintenance_status` — the status while in maintenance, zero otherwise: the
      product the `maint-status` rows linearize, so a unit in maintenance may also be off'
    dims: [snapshot, generator]
    where: Generator_maintainable AND Generator_committable
    absence: zero
    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 * (1 - Generator_maintenance_pu *
      Generator_maintenance)
  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 * (1 - Generator_maintenance_pu *
      Generator_maintenance)
  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_maintenance_pu
      * Generator_maintenance_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_maintenance_pu
      * Generator_maintenance_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_com_down_time:
    description: '`Generator-com-down-time` — a unit stopped within its own minimum down time is still
      off. The first snapshot''s share of the window is the brought-in down time''s, which the must-stay-down
      mask carries'
    dims: [snapshot, generator]
    where: Generator_committable AND Generator_min_down_time > 0 AND position(snapshot) > 0
    expression: sum_back(Generator_shut_down, along=snapshot, window=Generator_min_down_time) <= 1 - Generator_status
  Generator_com_status_must_stay_down:
    description: '`Generator-com-status-min_down_time_must_stay_up` — a unit still serving the down time
      it brought in stays off; PyPSA names the row `_must_stay_up`'
    dims: [snapshot, generator]
    where: Generator_committable AND Generator_must_stay_down
    expression: Generator_status == 0
  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_maint_event_count:
    description: '`Generator-maint-event-count` — a maintainable generator holds its number of maintenance
      events over the horizon'
    dims: [generator]
    where: Generator_maintainable
    expression: sum(Generator_maintenance_start, over=snapshot) == Generator_maintenance_events
  Generator_maint_window:
    description: '`Generator-maint-window` — a generator is in maintenance exactly where an event it started
      covers the snapshot; two events do not overlap, since the maintenance status is at most one'
    dims: [snapshot, generator]
    where: Generator_maintainable
    expression: Generator_maintenance == sum(Generator_maintenance_start, by=Generator_maintenance_cover,
      over=start, into=covered)
  Generator_maint_status_le_status:
    description: '`Generator-maint-status-le-status` — the status in maintenance is at most the status'
    dims: [snapshot, generator]
    where: Generator_maintainable AND Generator_committable
    expression: Generator_maintenance_status <= Generator_status
  Generator_maint_status_le_maint:
    description: '`Generator-maint-status-le-maint` — out of maintenance, the status in maintenance is
      zero'
    dims: [snapshot, generator]
    where: Generator_maintainable AND Generator_committable
    expression: Generator_maintenance_status <= Generator_maintenance
  Generator_maint_status_lb:
    description: '`Generator-maint-status-lb` — on and in maintenance, the status in maintenance is one'
    dims: [snapshot, generator]
    where: Generator_maintainable AND Generator_committable
    expression: Generator_maintenance_status >= Generator_status + Generator_maintenance - 1
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_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
  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
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_44_linearized_commitment.yaml', sources) as solution:
    solution.objective  # 7400.0

The network, rung_44_linearized_commitment.py in the corpus — the spine plus what this rung adds:

# SPDX-FileCopyrightText: mathspec Contributors
#
# SPDX-License-Identifier: MIT

"""Rung 44: linearized commitment with the rows the integer file has — a unit that must stay down, ramps read at the full build where a limit is missing, and maintenance."""

from __future__ import annotations

import spine

MODEL = 'pypsa_linearized_uc.yaml'
OPTIMIZE = {'linearized_unit_commitment': True}

#: a start costs more than a stop, so PyPSA does not tighten these units and the rung isolates the rows it adds
UNTIGHTENED = {'committable': True, 'up_time_before': 0, 'start_up_cost': 1}


def build():
    """The spine plus five cheap units on a north bus with a swinging load: each carries one of the rows under review."""
    n = spine.build()
    n.add(
        'Generator',
        'down44',
        bus='north',
        p_nom=40,
        marginal_cost=2,
        min_down_time=3,
        down_time_before=1,
        **UNTIGHTENED,
    )
    n.add('Generator', 'pulse44', bus='north', p_nom=40, marginal_cost=3, ramp_limit_start_up=0.4, **UNTIGHTENED)
    n.add(
        'Generator',
        'ramp44',
        bus='north',
        p_nom=40,
        marginal_cost=4,
        ramp_limit_up=0.5,
        ramp_limit_down=0.5,
        **UNTIGHTENED,
    )
    n.add(
        'Generator',
        'maint44',
        bus='north',
        p_nom=40,
        p_min_pu=0.3,
        marginal_cost=5,
        maintainable=True,
        maintenance_duration=2,
        **UNTIGHTENED,
    )
    n.add('Generator', 'fixed44', bus='north', p_nom=20, marginal_cost=1, maintainable=True, maintenance_duration=1)
    n.add('Load', 'swing44', bus='north', p_set=[40, 120, 160, 60])
    return n
n = build()
n.optimize(solver_name='highs')
n.objective  # 7400.0

The data

The tables this rung is the first to declare (5), as the prep produced them:

Generator_maintainable.csv

generator,value
coal,false
down44,false
fixed44,true
gas,false
maint44,true
pulse44,false
ramp44,false

Generator_maintenance_cover.csv

generator,start,covered
fixed44,2015-01-01T00:00:00.000000,2015-01-01T00:00:00.000000
fixed44,2015-01-01T01:00:00.000000,2015-01-01T01:00:00.000000
fixed44,2015-01-01T01:00:00.000000,2015-01-01T02:00:00.000000
fixed44,2015-01-01T02:00:00.000000,2015-01-01T02:00:00.000000
fixed44,2015-01-01T03:00:00.000000,2015-01-01T03:00:00.000000
maint44,2015-01-01T00:00:00.000000,2015-01-01T00:00:00.000000
maint44,2015-01-01T00:00:00.000000,2015-01-01T01:00:00.000000
maint44,2015-01-01T01:00:00.000000,2015-01-01T01:00:00.000000
maint44,2015-01-01T01:00:00.000000,2015-01-01T02:00:00.000000
maint44,2015-01-01T02:00:00.000000,2015-01-01T02:00:00.000000
maint44,2015-01-01T03:00:00.000000,2015-01-01T03:00:00.000000

Generator_maintenance_events.csv

generator,value
coal,1
down44,1
fixed44,1
gas,1
maint44,1
pulse44,1
ramp44,1

Generator_maintenance_pu.csv

generator,value
coal,1.0
down44,1.0
fixed44,1.0
gas,1.0
maint44,1.0
pulse44,1.0
ramp44,1.0

Generator_must_stay_down.csv

snapshot,generator,value
2015-01-01T00:00:00.000000,down44,true
2015-01-01T01:00:00.000000,down44,true