{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "bDLJBWVorXGZ"
},
"source": [
"```{index} single: application; energy systems\n",
"```\n",
"```{index} single: solver; highs\n",
"```\n",
"```{index} pandas dataframe\n",
"```\n",
"```{index} network optimization\n",
"```\n",
"```{index} stochastic optimization\n",
"```\n",
"```{index} SAA\n",
"```\n",
"\n",
"# Extra: Two-stage energy dispatch optimization with wind curtailment\n",
"\n",
"This notebook illustrates a two-stage stochastic optimization problem in the context of energy systems where there are recourse actions that depends on the realization of the uncertain parameters. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# install dependencies and select solver\n",
"%pip install -q amplpy matplotlib networkx numpy pandas\n",
"\n",
"SOLVER = \"highs\"\n",
"\n",
"from amplpy import AMPL, ampl_notebook\n",
"\n",
"ampl = ampl_notebook(\n",
" modules=[\"highs\"], # modules to install\n",
" license_uuid=\"default\", # license to use\n",
") # instantiate AMPL object and register magics"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "-lIzixzsd_do"
},
"source": [
"# Preliminaries\n",
"\n",
"The equilibrium point for the European power network, which operates on alternating current, is at a frequency of 50 Hertz with a tolerance threshold of +- 0.05 Hertz. Power network operators are responsible for maintaining a constant frequency on the network. The way that operators do this is mainly by perfectly balancing power supply and demand.\n",
"\n",
"But what happens if power supply does not meet demand? When _supply exceeds demand_, then the electrical frequency increases. If _demand exceeds supply_, then the frequency drops. Since power plants are designed to operate within a certain frequency range, there is a risk that they will disconnect from the grid after a period of time. If the frequency deviates too much from the 50 Hertz, then there is a risk that the power plants switch off one after another,potentially leading to a complete power blackout.\n",
"\n",
"Using conventional power generators such as coal and gas, power supply and demand are often matched by increasing/decreasing the production rate of their power plants and taking generating units on or off line. With the advent of renewable energy sources, it has become increasingly more difficult to match supply and demand. Besides not being controllable power sources, network operators rely on forecasting models to predict the power generated by renewable sources. In practice, this prediction is fairly accurate for solar energy, but wind energy is particularly difficult to predict correctly. \n",
"\n",
"\n",
"\n",
"The goal of this notebook is to ensure that power demand meets supply while taking into account wind fluctuations. We will introduce two optimization problems and solve them as stochastic optimization problems. **Read first the [energy dispatch problem](../04/power-network.ipynb) from the Chapter 4 for the preliminaries on power networks and the Optimal Power Flow (OPF) problem**. An important difference from the setting there, is that here we *will not assume that the wind generation is a decision variable*. Instead, wind generation is a random variable, as will be explained later on. We do assume that solar and hydro power are decision variables, since the former is accurately predicated whereas the latter is controllable."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "LeaVlnscEkFx"
},
"source": [
"\n",
"## Unit Commitment"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "3vJPW_Cyx2-k"
},
"source": [
"We now consider another optimization problem relevant for energy systems named the _Unit Commitment (UC)_ problem. UC is an extended version of the OPF problem in which an additional binary decision variable $x_i$ is introduced to decide whether generator $i$ should be activated or not. In this formulation we include a fixed cost $\\kappa_i$ that is incurred for the activation of a generator $i$, which is also added as a new parameter `c_fixed` to the network instance. \n",
"\n",
"In practice, the UC problem is often considered as a two-stage problem. Since it takes time to activate gas and coal generators before they can produce energy, this decision must be made in advance, i.e., as here-and-now decision in the first stage. In the second stage, we then decide the power generation levels of the (activated) coal and gas generators. Note that, in particular, *we cannot produce from generators we did not already turn on!* Lastly, the UC still includes the same physical constraints as the OPF, i.e., power generation limits and line capacity constraints.\n",
"\n",
"All solar and hydro generators are activated by default. Solar power is treated as deterministic in the sense that in the instance that you are given it holds that $p_{\\min}=p_{\\max}$ for solar generators. Hydro generators are still controllable within their nontrivial generation limits.\n",
"\n",
"The uncertainty will be described in terms of the _wind speed_ $v$ instead of the wind power. To determine the wind power resulting from a given wind speed, you need to use a so-called _power curve_ $g_i(\\cdot)$ for a wind generator $i$, which is an explicit function that maps the wind speed $v$ to a wind power $g_i(v)$. In the network instance that you are given, there are two wind generators, one in node 64 which is an on-shore wind park, and one in node 65 which is a off-shore wind park. Being structurally different, they have different power curves. See below a plot of the power curve function for the on-shore wind generator. \n",
"\n",
"\n",
"
\n", " | \n", " | b | \n", "f_max | \n", "
---|---|---|---|
node_id1 | \n", "node_id2 | \n", "\n", " | \n", " |
0 | \n", "1 | \n", "10.0100 | \n", "270.705509 | \n", "
2 | \n", "23.5849 | \n", "415.734756 | \n", "|
3 | \n", "4 | \n", "125.3133 | \n", "265.273978 | \n", "
2 | \n", "4 | \n", "9.2593 | \n", "400.159230 | \n", "
4 | \n", "5 | \n", "18.5185 | \n", "217.852748 | \n", "
... | \n", "... | \n", "... | \n", "... | \n", "
64 | \n", "65 | \n", "28.9059 | \n", "1200.000000 | \n", "
67 | \n", "68 | \n", "28.9059 | \n", "1200.000000 | \n", "
80 | \n", "79 | \n", "28.9059 | \n", "1200.000000 | \n", "
86 | \n", "85 | \n", "4.8216 | \n", "602.814908 | \n", "
67 | \n", "115 | \n", "246.9136 | \n", "1200.000000 | \n", "
179 rows × 2 columns
\n", "\n", " | d | \n", "p_min | \n", "p_max | \n", "c_var | \n", "is_generator | \n", "energy_type | \n", "c_fixed | \n", "
---|---|---|---|---|---|---|---|
node_id | \n", "\n", " | \n", " | \n", " | \n", " | \n", " | \n", " | \n", " |
0 | \n", "44.230344 | \n", "0.0 | \n", "0.0 | \n", "0 | \n", "False | \n", "\n", " | 0 | \n", "
1 | \n", "17.690399 | \n", "0.0 | \n", "0.0 | \n", "0 | \n", "False | \n", "\n", " | 0 | \n", "
2 | \n", "35.514209 | \n", "0.0 | \n", "0.0 | \n", "0 | \n", "False | \n", "\n", " | 0 | \n", "
3 | \n", "35.248977 | \n", "0.0 | \n", "0.0 | \n", "0 | \n", "False | \n", "\n", " | 0 | \n", "
4 | \n", "0.000000 | \n", "0.0 | \n", "0.0 | \n", "0 | \n", "False | \n", "\n", " | 0 | \n", "
... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "
113 | \n", "6.784856 | \n", "0.0 | \n", "0.0 | \n", "0 | \n", "False | \n", "\n", " | 0 | \n", "
114 | \n", "19.664045 | \n", "0.0 | \n", "0.0 | \n", "0 | \n", "False | \n", "\n", " | 0 | \n", "
115 | \n", "163.009206 | \n", "0.0 | \n", "0.0 | \n", "0 | \n", "False | \n", "\n", " | 0 | \n", "
116 | \n", "16.175633 | \n", "0.0 | \n", "0.0 | \n", "0 | \n", "False | \n", "\n", " | 0 | \n", "
117 | \n", "31.504459 | \n", "0.0 | \n", "0.0 | \n", "0 | \n", "False | \n", "\n", " | 0 | \n", "
118 rows × 7 columns
\n", "\n", " | d | \n", "p_min | \n", "p_max | \n", "c_var | \n", "is_generator | \n", "energy_type | \n", "c_fixed | \n", "
---|---|---|---|---|---|---|---|
node_id | \n", "\n", " | \n", " | \n", " | \n", " | \n", " | \n", " | \n", " |
9 | \n", "0.0 | \n", "0.000000 | \n", "400.000000 | \n", "0 | \n", "True | \n", "hydro | \n", "0 | \n", "
11 | \n", "0.0 | \n", "0.000000 | \n", "200.000000 | \n", "0 | \n", "True | \n", "hydro | \n", "0 | \n", "
24 | \n", "0.0 | \n", "0.000000 | \n", "422.086431 | \n", "28 | \n", "True | \n", "coal | \n", "1689 | \n", "
25 | \n", "0.0 | \n", "0.000000 | \n", "227.384370 | \n", "18 | \n", "True | \n", "coal | \n", "1057 | \n", "
30 | \n", "0.0 | \n", "0.000000 | \n", "235.306239 | \n", "19 | \n", "True | \n", "coal | \n", "1837 | \n", "
45 | \n", "0.0 | \n", "0.000000 | \n", "371.349675 | \n", "19 | \n", "True | \n", "coal | \n", "1456 | \n", "
48 | \n", "0.0 | \n", "227.262510 | \n", "227.262510 | \n", "0 | \n", "True | \n", "solar | \n", "0 | \n", "
53 | \n", "0.0 | \n", "97.526012 | \n", "97.526012 | \n", "0 | \n", "True | \n", "solar | \n", "0 | \n", "
58 | \n", "0.0 | \n", "284.753966 | \n", "284.753966 | \n", "0 | \n", "True | \n", "solar | \n", "0 | \n", "
60 | \n", "0.0 | \n", "98.693808 | \n", "98.693808 | \n", "0 | \n", "True | \n", "solar | \n", "0 | \n", "
64 | \n", "0.0 | \n", "16.050352 | \n", "16.050352 | \n", "0 | \n", "True | \n", "wind | \n", "0 | \n", "
65 | \n", "0.0 | \n", "77.747257 | \n", "77.747257 | \n", "0 | \n", "True | \n", "wind | \n", "0 | \n", "
79 | \n", "0.0 | \n", "0.000000 | \n", "360.554867 | \n", "38 | \n", "True | \n", "gas | \n", "1504 | \n", "
86 | \n", "0.0 | \n", "0.000000 | \n", "445.397099 | \n", "38 | \n", "True | \n", "gas | \n", "1751 | \n", "
88 | \n", "0.0 | \n", "0.000000 | \n", "298.390683 | \n", "33 | \n", "True | \n", "gas | \n", "2450 | \n", "
99 | \n", "0.0 | \n", "0.000000 | \n", "440.534057 | \n", "33 | \n", "True | \n", "gas | \n", "2184 | \n", "
102 | \n", "0.0 | \n", "0.000000 | \n", "454.660581 | \n", "35 | \n", "True | \n", "gas | \n", "1617 | \n", "
110 | \n", "0.0 | \n", "0.000000 | \n", "451.133883 | \n", "34 | \n", "True | \n", "gas | \n", "1627 | \n", "