Skip to content

Commit e59cdf0

Browse files
committed
Add docs for unit commitment
Signed-off-by: Davey Elder <iandavidelder@gmail.com>
1 parent 6aa9e73 commit e59cdf0

2 files changed

Lines changed: 266 additions & 0 deletions

File tree

docs/source/extensions.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,4 @@ as new extensions are added.
246246
extensions/growth_rates
247247
extensions/discrete_capacity
248248
extensions/eos
249+
extensions/unit_commitment
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
.. _extension-unit-commitment:
2+
3+
Unit Commitment
4+
===============
5+
6+
The **unit_commitment** extension adds commitment-level operational constraints
7+
to selected technologies: online/started/stopped unit accounting, minimum
8+
output floors, maximum output ceilings (replacing the default capacity
9+
constraint), and minimum up-time and down-time windows. It also supports
10+
discounted startup costs and startup emissions and input flows.
11+
12+
It is disabled by default and enabled per run through configuration:
13+
14+
.. code-block:: toml
15+
16+
extensions = ["unit_commitment"]
17+
18+
Conceptual Model
19+
----------------
20+
21+
The core idea is that the continuous capacity variable :math:`\textbf{CAP}_{r,p,t,v}` is
22+
*notionally divided* into discrete units, each of size :math:`UC_{r,t}`. The number of
23+
units available is therefore :math:`\textbf{CAP}_{r,p,t,v} / UC_{r,t}`. Crucially,
24+
:math:`\textbf{CAP}` itself remains a standard linear (continuous) Pyomo variable — the
25+
extension does not change its domain. In practice, because the UC constraints tie output
26+
directly to the integer count of online units, the optimiser will typically land on a
27+
solution where :math:`\textbf{CAP}` is an integer multiple of :math:`UC_{r,t}`, but this
28+
is a consequence of the optimisation rather than an explicit constraint.
29+
30+
The three UC variables — :math:`\textbf{UCN}` (online), :math:`\textbf{UCST}` (started),
31+
and :math:`\textbf{UCSP}` (stopped) — count whole units and are promoted to non-negative
32+
integers by default (MIP). In **linearized mode** (:code:`linearized = 1`), these
33+
variables are kept continuous. This is equivalent to imagining that :math:`UC_{r,t}` is
34+
infinitesimally small: the commitment variables become fractional occupancy factors rather
35+
than discrete unit counts, and the min-output/max-output bounds act as simple lower and
36+
upper capacity-factor constraints without any integer requirements. Linearized mode is
37+
useful for reducing solve time during early model development or sensitivity analysis.
38+
39+
.. warning::
40+
41+
**Annual technologies are incompatible with unit commitment.** Technologies
42+
in the :code:`tech_annual` set have no time-slice flexibility and cannot be
43+
committed; the model will raise an error if any annual technology appears in
44+
the :code:`unit_commitment` table.
45+
46+
.. warning::
47+
48+
**Integer vs. linear relaxation.** By default all three UC variables
49+
(:math:`\textbf{UCN}`, :math:`\textbf{UCST}`, :math:`\textbf{UCSP}`) are
50+
promoted to non-negative integers, turning the problem into a MIP. Set
51+
:code:`linearized = 1` in the :code:`unit_commitment` table for a
52+
particular technology to keep those variables continuous (LP relaxation).
53+
54+
.. note::
55+
56+
The :code:`uc_max_output_constraint` **replaces** the default
57+
:code:`capacity_constraint` for processes listed in the
58+
:code:`unit_commitment` table. Non-UC processes retain the standard
59+
capacity constraint unchanged.
60+
61+
Overview
62+
--------
63+
64+
.. list-table::
65+
:header-rows: 1
66+
:widths: 28 38 34
67+
68+
* - Table
69+
- Purpose
70+
- Key interaction
71+
* - :code:`unit_commitment`
72+
- Core UC parameters (unit size, output fractions, up/down times).
73+
- Enables UC constraints; replaces capacity constraint for UC techs.
74+
Commitment results are written to :code:`output_unit_commitment`.
75+
* - :code:`unit_commitment_startup_cost`
76+
- Direct startup cost per unit of capacity started.
77+
- Added to variable costs.
78+
* - :code:`unit_commitment_startup_emissions`
79+
- Emissions produced at startup per unit of capacity started.
80+
- Added to :code:`limit_emission_constraint` totals, emission
81+
costs and :code:`output_emission` table.
82+
* - :code:`unit_commitment_startup_input`
83+
- Input commodity consumed at startup per unit of capacity started.
84+
- Added to consumption in :code:`commodity_balance_constraint` and
85+
results in :code:`output_flow_in`.
86+
87+
Parameters
88+
----------
89+
90+
unit_commitment
91+
~~~~~~~~~~~~~~~
92+
93+
:math:`{UC}_{r \in R,\, t \in T}`
94+
95+
The primary UC table. Each row registers a technology (or technology group)
96+
as subject to unit-commitment constraints and specifies all operational
97+
parameters. Exactly one row per :math:`(r, t)` pair is required.
98+
99+
.. csv-table::
100+
:header: "Column", "Symbol", "Default", "Description"
101+
:widths: 24, 22, 10, 44
102+
103+
":code:`region`", ":math:`r`", "—", "region label"
104+
":code:`tech`", ":math:`t`", "—", "technology name"
105+
":code:`unit_capacity`", ":math:`UC_{r,t}`", "—", "nameplate capacity per discrete unit (same units as :code:`v_capacity`); **required**"
106+
":code:`min_output_fraction`", ":math:`\underline{f}_{r,t}`", "0", "minimum output as a fraction of available nameplate output per online unit; :math:`[0, 1]`"
107+
":code:`max_output_fraction`", ":math:`\overline{f}_{r,t}`", "1", "maximum output as a fraction of available nameplate output per online unit; :math:`(0, 1]`"
108+
":code:`min_up_time_hours`", ":math:`T^{up}_{r,t}`", "0", "minimum number of consecutive hours a unit must remain online after starting; must be less than one full day"
109+
":code:`min_down_time_hours`", ":math:`T^{dn}_{r,t}`", "0", "minimum number of consecutive hours a unit must remain offline after stopping; must be less than one full day"
110+
":code:`linearized`", "—", "0", "if 1, UC variables are kept continuous (LP relaxation); if 0, they are promoted to non-negative integers (MIP)"
111+
112+
unit_commitment_startup_cost
113+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
114+
115+
:math:`{UCSC}_{r \in R,\, t \in T}`
116+
117+
Optional. Specifies a cost incurred each time a unit is started, expressed
118+
per unit of capacity. The total startup cost in a timeslice :math:`(s, d)` is:
119+
120+
.. math::
121+
122+
\text{startup cost} = \textbf{UCST}_{r,p,s,d,t,v} \cdot UC_{r,t} \cdot UCSC_{r,t}
123+
124+
.. csv-table::
125+
:header: "Column", "Symbol", "Description"
126+
:widths: 22, 20, 58
127+
128+
":code:`region`", ":math:`r`", "region label"
129+
":code:`tech`", ":math:`t`", "technology name"
130+
":code:`cost_per_cap`", ":math:`UCSC_{r,t}`", "startup cost per unit of capacity (same currency units as :code:`cost_invest`)"
131+
132+
unit_commitment_startup_emissions
133+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
134+
135+
:math:`{UCSE}_{r \in R,\, e \in C^e,\, t \in T}`
136+
137+
Optional. Specifies an emission produced at startup per unit of capacity
138+
started. Startup emissions are summed into the :code:`limit_emission_constraint`
139+
alongside normal activity-based emissions.
140+
141+
.. csv-table::
142+
:header: "Column", "Symbol", "Description"
143+
:widths: 22, 20, 58
144+
145+
":code:`region`", ":math:`r`", "region label"
146+
":code:`emis_comm`", ":math:`e`", "emission commodity"
147+
":code:`tech`", ":math:`t`", "technology name"
148+
":code:`emis_per_cap`", ":math:`UCSE_{r,e,t}`", "emission per unit of capacity started (same units as :code:`emission_activity`)"
149+
150+
unit_commitment_startup_input
151+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
152+
153+
:math:`{UCSI}_{r \in R,\, i \in C^p,\, t \in T}`
154+
155+
Optional. Specifies a physical commodity consumed at startup per unit of
156+
capacity started. Startup inputs are summed into the
157+
:code:`commodity_balance_constraint` alongside normal flow-based consumption.
158+
159+
.. csv-table::
160+
:header: "Column", "Symbol", "Description"
161+
:widths: 22, 20, 58
162+
163+
":code:`region`", ":math:`r`", "region label"
164+
":code:`input_comm`", ":math:`i`", "input commodity"
165+
":code:`tech`", ":math:`t`", "technology name"
166+
":code:`input_per_cap`", ":math:`UCSI_{r,i,t}`", "input commodity consumed per unit of capacity started"
167+
168+
Decision Variables
169+
------------------
170+
171+
Three UC decision variables are added per :math:`(r, p, s, d, t, v)` index.
172+
By default they are non-negative integers (MIP); set :code:`linearized = 1`
173+
to relax them to continuous non-negative reals.
174+
175+
.. csv-table::
176+
:header: "Variable", "Domain", "Description"
177+
:widths: 36, 16, 48
178+
179+
":math:`\textbf{UCN}_{r,p,s,d,t,v}` (:code:`v_uc_online`)", ":math:`\mathbb{Z}_{\ge 0}`", "number of units online at the start of timeslice :math:`(s,d)`"
180+
":math:`\textbf{UCST}_{r,p,s,d,t,v}` (:code:`v_uc_started`)", ":math:`\mathbb{Z}_{\ge 0}`", "number of units that start up during timeslice :math:`(s,d)`"
181+
":math:`\textbf{UCSP}_{r,p,s,d,t,v}` (:code:`v_uc_stopped`)", ":math:`\mathbb{Z}_{\ge 0}`", "number of units that shut down during timeslice :math:`(s,d)`"
182+
183+
Constraints
184+
-----------
185+
186+
Unit Count Bounds
187+
~~~~~~~~~~~~~~~~~
188+
189+
.. autofunction:: temoa.extensions.unit_commitment.components.commitment.uc_online_upper_constraint
190+
191+
.. autofunction:: temoa.extensions.unit_commitment.components.commitment.uc_started_upper_tightening_constraint
192+
193+
.. autofunction:: temoa.extensions.unit_commitment.components.commitment.uc_stopped_upper_tightening_constraint
194+
195+
Commitment Transition
196+
~~~~~~~~~~~~~~~~~~~~~
197+
198+
.. autofunction:: temoa.extensions.unit_commitment.components.commitment.uc_transition_constraint
199+
200+
Output Bounds
201+
~~~~~~~~~~~~~
202+
203+
.. autofunction:: temoa.extensions.unit_commitment.components.commitment.uc_min_output_constraint
204+
205+
.. autofunction:: temoa.extensions.unit_commitment.components.commitment.uc_max_output_constraint
206+
207+
Minimum Up/Down Time
208+
~~~~~~~~~~~~~~~~~~~~
209+
210+
.. autofunction:: temoa.extensions.unit_commitment.components.commitment.uc_min_up_time_constraint
211+
212+
.. autofunction:: temoa.extensions.unit_commitment.components.commitment.uc_min_down_time_constraint
213+
214+
Ramp Constraints
215+
~~~~~~~~~~~~~~~~
216+
217+
When a technology appears in both the :code:`unit_commitment` table and the
218+
:code:`ramp_up_hourly` / :code:`ramp_down_hourly` tables, the standard core-model
219+
ramp constraints (:code:`ramp_up_constraint`, :code:`ramp_down_constraint`) are
220+
**replaced** by UC-aware versions for that technology. Non-UC technologies retain
221+
the original core-model ramp constraints unchanged.
222+
223+
The core ramp constraint bounds the change in hourly activity against a fraction of
224+
total installed capacity. The UC version replaces this with a commitment-aware
225+
expression: only online units contribute to the ramp envelope, and the operating
226+
point assumed for units that start or stop during the transition adds or removes
227+
headroom according to the effective minimum output fraction. See
228+
:func:`~temoa.extensions.unit_commitment.components.commitment._ramp_constraint`
229+
for the full mathematical derivation.
230+
231+
.. autofunction:: temoa.extensions.unit_commitment.components.commitment.uc_ramp_up_constraint
232+
233+
.. autofunction:: temoa.extensions.unit_commitment.components.commitment.uc_ramp_down_constraint
234+
235+
.. autofunction:: temoa.extensions.unit_commitment.components.commitment._ramp_constraint
236+
237+
Objective Contributions
238+
-----------------------
239+
240+
.. autofunction:: temoa.extensions.unit_commitment.components.startup.append_startup_costs
241+
242+
Output Table
243+
------------
244+
245+
Results are written to the :code:`output_unit_commitment` table with one row per
246+
:math:`(scenario, region, period, season, tod, tech, vintage)`:
247+
248+
.. csv-table::
249+
:header: "Column", "Description"
250+
:widths: 22, 78
251+
252+
":code:`online_cap`", "capacity online at the start of the timeslice (:math:`\textbf{UCN} \cdot UC_{r,t}`)"
253+
":code:`start_cap`", "capacity started during the timeslice (:math:`\textbf{UCST} \cdot UC_{r,t}`)"
254+
":code:`stop_cap`", "capacity stopped during the timeslice (:math:`\textbf{UCSP} \cdot UC_{r,t}`)"
255+
256+
.. note::
257+
258+
**Starts and stops take effect in the following time slice.** The transition
259+
constraint links :math:`\textbf{UCST}_{s,d}` and :math:`\textbf{UCSP}_{s,d}` to the
260+
*change* in online count between :math:`(s,d)` and its successor
261+
:math:`(s_{next}, d_{next})`. A unit started during slice :math:`(s,d)` therefore
262+
first appears as online — and is first able to produce output — in
263+
:math:`(s_{next}, d_{next})`. Equivalently, a unit stopped during :math:`(s,d)` is
264+
still counted as online for that slice and goes offline from
265+
:math:`(s_{next}, d_{next})` onward.

0 commit comments

Comments
 (0)