15 Thermal Generation and Reserve Capacity
Based on Tariff rates (power generation), Section 12.15 of Williams (2013). New three-generator, three-period daily dispatch instance. Initial units are off and end-of-day shutdown is not imposed. The results below solve the stated instance and are not presented as the numerical answer to an unmodified textbook problem.
15.1 Problem Statement
An industrial park requires 50, 140, and 100 MW in three consecutive 8-hour periods. Thermal units A,B,C have minimum outputs (20,20,30) MW, maximum outputs (60,60,100) MW, fixed online costs (30,30,60) USD/h, marginal energy costs (2,2.5,3) USD/MWh, and startup costs (200,200,150) USD/start. All units are off before period 1. Each period must have online maximum capacity at least 115% of demand, although dispatched power equals demand. No minimum up/down times or ramp restrictions are imposed. Choose online states, startups, and dispatch to minimize total daily cost. There is no terminal online-state requirement.
15.1.1 Generating units
| Unit | Minimum (MW) | Maximum (MW) | Online (USD/h) | Energy (USD/MWh) | Startup (USD) |
|---|---|---|---|---|---|
| A | 20 | 60 | 30 | 2 | 200 |
| B | 20 | 60 | 30 | 2.5 | 200 |
| C | 30 | 100 | 60 | 3 | 150 |
15.1.2 Load profile
| Period | Duration (h) | Demand (MW) |
|---|---|---|
| 1 | 8 | 50 |
| 2 | 8 | 140 |
| 3 | 8 | 100 |
15.2 Model Formulation
Power \(p_{gt}\ge0\) is continuous MW, online state \(y_{gt}\) and startup \(u_{gt}\) are binary. Initial state is \(y_{g0}=0\).
\[P_g^{\min}y_{gt}\le p_{gt}\le P_g^{\max}y_{gt},\quad\sum_gp_{gt}=D_t,\quad\sum_gP_g^{\max}y_{gt}\ge1.15D_t.\] \[u_{gt}\ge y_{gt}-y_{g,t-1},\quad u_{gt}\le y_{gt},\quad u_{gt}\le1-y_{g,t-1}.\] \[\min\sum_{gt}\left[8(F_gy_{gt}+c_gp_{gt})+S_gu_{gt}\right].\]
The factor 8 converts power and hourly fixed costs into period energy costs. Reserve is unused online capability; it is not extra scheduled generation. This MILP models commitment and dispatch jointly.
All decision variables and units refer to the problem statement above. Continuous variables are nonnegative unless explicitly stated otherwise; binary and integer domains are specified in the equations and code.
15.3 Pyomo Implementation
The following Python implementation uses Pyomo and HiGHS. Run from the workbook root so that the models package and shared helpers are importable. The shared solver and audit functions check optimal termination before loading values, then verify every active constraint, variable bound, and integer domain. Any imported earlier-chapter model supplies the data and balances already explained there.
The full source is problem15.py. To solve and print this problem independently:
~/.venvs/optim/bin/python -m models.common 15import pyomo.environ as pyo
from models.common import frame, solve
MIN = [20, 20, 30]
MAX = [60, 60, 100]
FIX = [30, 30, 60]
COST = [2, 2.5, 3]
START = [200, 200, 150]
DEMAND = [50, 140, 100]
def build(reserve=0.15):
m = pyo.ConcreteModel()
m.G = pyo.RangeSet(0, 2)
m.T = pyo.RangeSet(0, 2)
m.p = pyo.Var(m.G, m.T, domain=pyo.NonNegativeReals)
m.y = pyo.Var(m.G, m.T, domain=pyo.Binary)
m.u = pyo.Var(m.G, m.T, domain=pyo.Binary)
m.c = pyo.ConstraintList()
for g in m.G:
for t in m.T:
previous = m.y[g, t - 1] if t else 0
m.c.add(m.p[g, t] >= MIN[g] * m.y[g, t])
m.c.add(m.p[g, t] <= MAX[g] * m.y[g, t])
m.c.add(m.u[g, t] >= m.y[g, t] - previous)
m.c.add(m.u[g, t] <= m.y[g, t])
m.c.add(m.u[g, t] <= 1 - previous)
m.balance = pyo.Constraint(
m.T, rule=lambda m, t: sum(m.p[g, t] for g in m.G) == DEMAND[t]
)
m.reserve = pyo.Constraint(
m.T,
rule=lambda m, t: (
sum(MAX[g] * m.y[g, t] for g in m.G) >= (1 + reserve) * DEMAND[t]
),
)
m.obj = pyo.Objective(
expr=sum(
8 * (FIX[g] * m.y[g, t] + COST[g] * m.p[g, t]) + START[g] * m.u[g, t]
for g in m.G
for t in m.T
)
)
return m
def check(m):
for t in m.T:
assert abs(sum(pyo.value(m.p[g, t]) for g in m.G) - DEMAND[t]) < 1e-6
def tables(m):
no_reserve = solve(build(0))
return {
"dispatch": frame(
[
[
t + 1,
"ABC"[g],
pyo.value(m.y[g, t]),
pyo.value(m.u[g, t]),
pyo.value(m.p[g, t]),
]
for t in m.T
for g in m.G
],
["Period", "Unit", "Online", "Startup", "Power (MW)"],
),
"reserve_cost": frame(
[
["15% reserve", pyo.value(m.obj)],
["No reserve", pyo.value(no_reserve.obj)],
],
["Policy", "Daily cost (USD)"],
),
}
def plot(m):
return (
["A", "B", "C"],
[8 * sum(pyo.value(m.p[g, t]) for t in m.T) for g in m.G],
"Daily energy (MWh)",
)
# Solve, audit constraints, and run domain-specific checks.
from models.common import solve, audit
model = solve(build())
audit(model)
check(model)
for name, result_table in tables(model).items():
print(name)
print(result_table.to_string(index=False))15.4 Optimal Solution
The solver reports optimal termination. The objective is 7,470 USD/day (minimize). The largest violation across active constraints, variable bounds, and integer domains is 0.00e+00 in the corresponding model units. The problem-specific checks also pass. These checks establish numerical consistency with the stated model, not the validity of its assumptions for a real facility.
15.4.1 Dispatch
| Period | Unit | Online | Startup | Power (MW) |
|---|---|---|---|---|
| 1 | A | 1 | 1 | 50 |
| 1 | B | 0 | 0 | 0 |
| 1 | C | 0 | 0 | 0 |
| 2 | A | 1 | 0 | 60 |
| 2 | B | 1 | 1 | 50 |
| 2 | C | 1 | 1 | 30 |
| 3 | A | 1 | 0 | 60 |
| 3 | B | 1 | 0 | 40 |
| 3 | C | 0 | 0 | 0 |
15.4.2 Reserve cost
| Policy | Daily cost (USD) |
|---|---|
| 15% reserve | 7,470 |
| No reserve | 7,430 |
Tables round numerical values for reading; feasibility checks use the original solver values. Multiple optimal decisions may exist. Machine-readable result records the solver status and package versions. Figure-generation code is in figures/workbook.py.
15.5 Brief Discussion
The 15% reserve policy changes daily cost by 40.00 USD relative to the re-solved zero-reserve case.
A unit may be online to provide reserve while another supplies cheaper energy. Startup charges couple consecutive periods. A reserve-policy cost is a re-solved scenario difference, not a universal electricity tariff. Experiment: reduce reserve to zero and compare daily cost and the commitment schedule.