Unit Commitment for Electrical Power Generation
Description: This notebook illustrates the power generation problem using AMPL. The original version featured the Gurobi solver. By default, this notebook uses the HiGHS and CBC solvers.
Major electric power companies around the world utilize mathematical optimization to manage the flow of energy across their electrical grids. In this example, you’ll discover the power of mathematical optimization in addressing a common energy industry problem: unit commitment for electrical power generation. We’ll show you how to figure out the optimal set of power stations to turn on in order to satisfy anticipated power demand over a 24-hour time horizon.
This model is example 15 from the fifth edition of Model Building in Mathematical Programming by H. Paul Williams on pages 270 – 271 and 325 – 326.
This example is at the intermediate level, where we assume that you know Python and the AMPL’s Python API and that you have some knowledge of building mathematical optimization models.
Tags: amplpy, energy, power-generation, unit-commitment
Notebook author: Gyorgy Matyasfalvi <gyorgy@ampl.com>
References:
Electrical power generation 1 notebook
H. Paul Williams, Model Building in Mathematical Programming, fifth edition.
Problem Description
In this problem, power generation units are grouped into three distinct types, with different characteristics for each type (power output, cost per megawatt hour, startup cost, etc.). A unit can be on or off, with a startup cost associated with transitioning from off to on, and power output that can fall anywhere between a specified minimum and maximum value when the unit is on. A 24-hour time horizon is divided into 5 discrete time periods, each with an expected total power demand. The model decides which units to turn on, and when, in order to satisfy demand for each time period. The model also captures a reserve requirement, where the selected power plants must be capable of increasing their output, while still respecting their maximum output, in order to cope with the situation where actual demand exceeds predicted demand.
A set of generators is available to satisfy power demand for the following day. Anticipated demand is as follows:
Time Period |
Demand (megawatts) |
12 am to 6 am |
15000 |
6 am to 9 am |
30000 |
9 am to 3 pm |
25000 |
3 pm to 6 pm |
40000 |
6 pm to 12 am |
27000 |
Generators are grouped into three types, with the following minimum and maximum output for each type (when they are on):
Type |
Number available |
Minimum output (MW) |
Maximum output (MW) |
wind |
12 |
850 |
2000 |
gas |
10 |
1250 |
1750 |
hydro |
5 |
1500 |
4000 |
There are costs associated with using a generator: a cost per hour when the generator is on (and generating its minimum output), a cost per megawatt hour above its minimum, and a startup cost for turning a generator on:
Type |
Cost per hour (when on) |
Cost per MWh above minimum |
Startup cost |
wind |
$$1000$ |
$$2.00$ |
$$2000$ |
gas |
$$2600$ |
$$1.30$ |
$$1000$ |
hydro |
$$3000$ |
$$3.00$ |
$$500$ |
Generators must meet predicted demand, but they must also have sufficient reserve capacity to be able to cope with the situation where actual demand exceeds predicted demand. For this model, the set of selected generators must be able to produce as much as 115% of predicted demand.
Which generators should be committed to meeting anticipated demand in order to minimize total cost?
Reset AMPL
Needed to allow for repeated runs
Model Development
We first create the sets, parameters, and the variables in AMPL. For each time period, we have: an integer decision variable to capture the number of active generators of each type (ngen), an integer variable to capture the number of generators of each type we must start (nstart), and a continuous decision variable to capture the total power output for each generator type (output).
Load data directly from Python data structures using amplpy
We define all the input data of the model and send it to AMPL.
It’s good practice to first load the data of all indexing sets into AMPL as it allows us to send data for indexed entities more readily in later stages.
Next we insert the constraints:
The number of active generators can’t exceed the number of generators.
Total power output for a generator type depends on the number of generators of that type that are active.
Total output for each time period must meet predicted demand.
Selected generators must be able to cope with an excess of demand.
Connect the decision variables that capture active generators with the decision variables that count startups.
Objective: minimize total cost. Cost consists of three components: the cost for running active generation units, the cost to generate power beyond the minimum for each unit, and the cost to start up generation units.
Set option solver and solve
Solve with HiGHS:
HiGHS 1.5.1: HiGHS 1.5.1: optimal solution; objective 1002540
38 simplex iterations
1 branching nodes
Solve with CBC:
cbc 2.10.7: cbc 2.10.7: optimal solution; objective 1002540
7 simplex iterations
7 barrier iterations
Analysis
The anticipated demand for electricity over the 24-hour time window can be met for a total cost of $$1,002,540$. The detailed plan for each time period is as follows.
Unit Commitments
The following table shows the number of generators of each type that are active in each time period in the optimal solution using AMPL’s display
command:
ngen [*,*]
: '12 am to 6 am' '6 am to 9 am' '9 am to 3 pm' '3 pm to 6 pm' '6 pm to 12 am' :=
gas 12 12 12 12 12
hydro 3 8 8 9 9
wind 0 0 0 2 0
;
The following shows the number of generators of each type that must be started in each time period to achieve this plan (recall that the model assumes that up to 5 generators of each type are available at the beginning of the time horizon). Here we use Pandas DataFrame and IPython’s display()
function:
|
12 am to 6 am |
6 am to 9 am |
9 am to 3 pm |
3 pm to 6 pm |
6 pm to 12 am |
gas |
7.0 |
0.0 |
0.0 |
0.0 |
0.0 |
hydro |
0.0 |
5.0 |
0.0 |
1.0 |
0.0 |
wind |
0.0 |
0.0 |
0.0 |
2.0 |
0.0 |