Skip to content

Unit Commitment

Definition

The Unit Commitment (UC) module in KPG Platform provides a framework for scheduling and operating power plants to reliably meet electricity demand at the lowest possible cost. Unlike simple economic dispatch, UC explicitly accounts for generator on/off decisions, minimum up/down times, start-up costs, and ramping limits. By modeling these operational realities, UC helps illustrate how different generation resources are coordinated over time to balance supply and demand while maintaining system reliability.

Mathematical formulation 🧮

Equation explanation

(1a): Objective function
This function minimizes the total operation cost over all generators in set for time period .
It includes generation cost , startup cost , shutdown cost .

(1b): Minimum Generation Limit Constraint
This constraint ensures that if a generator is online , its net dispatch minus downward reserve is no less than its minimum generation limit .

(1c): Maximum Generation Limit Constraint with Startup & Shutdown Adjustment
This constraint restricts the generator’s net output plus upward reserve from exceeding the maximum capacity. It also adjusts the upper bound depending on whether the unit is starting up or shutting down .

(1d): Ramp-Up Constraint
This constraint limits the increase in generator output (including upward reserve) between two consecutive time periods to not exceed the generator’s ramp-up capability .

(1e): Ramp-down Constraint
This constraint restricts the decrease in generator output (including downward reserve) to remain within the ramp-down capability .

(1f): Minimum Up-Time Constraint
Once a unit has been turned on, it must remain online for at least time periods.
This constraint prevents the unit from shutting down too soon after startup.

(1g): Minimum Down-Time Constraint
Once a unit is shut down, it must remain offline for at least time periods.
This constraint ensures sufficient recovery time before restarting.

(1h): Logical Contraint
This constraint guarantees that and take the appropriate values when the unit starts up or shuts down. It ensures consistency in unit status transition.

(1i): System Upward Reserve Requirement
This constraint ensures that the system-wide upward reserve requirement at each time is satisfied by the sum of available upward reserves from all units.

(1j): System Downward Reserve Requirement
This constraint guarantees that the system-wide downward reserve requirement is met by aggregating the downward reserves provided by all units.

(1k): Generation-load Balance Contraint
This constraint enforces that total generation must equal total demand across all buses at each time . It ensures system-wide power balance.

Julia code 🔢

# Data for the problem
genset = 1:3
T = 6
timeset = 1:T
# Costs
CU = [800, 500, 250] # Startup cost
CD = [200, 300, 400] # Shutdown cost
C0 = [0 for i in genset]
C1 = [5 15 30] # Linear generation cost
C2 = [0 for i in genset]
# Unit 1: most expensive CU and cheapest C1
# Unit 2: moderate CU and C1
# Unit 3: Cheapest CU and most expensive C1
# The intuition here is that the UC problem will use unit 1 first, then unit 2, and finally unit 3
# Generation limits (Unit 1 > Unit 2 > Unit 3)
Pmin = [80, 50, 30]
Pmax = [300, 200, 100]
# Start-up and shutdown ramp rate (Unit 1 > Unit 2 > Unit 3)
SU = [100, 70, 40]
SD = [80, 50, 30]
# Ramp-up and ramp-down limits (Unit 3 > Unit 2 > Unit 1)
RU = [50, 60, 70]
RD = [30, 40, 50]
# Minimum up- and down-time
TU = [3, 2, 1]
TD = [2, 2, 2]
# Initial state
# Unit 1 has to be turned on for 2 hours from its initial state
u0 = [1, 0, 0]
P0 = [120, 0, 0]
TU0 = [2, 0, 0]
TD0 = [0, 0, 0]
# Demand and fixed reserve requirement
D = [220, 250, 200, 170, 230, 190]
R = [10 for i in timeset]
# Optimization model
using JuMP, HiGHS
m = Model(HiGHS.Optimizer)
# Variables
@variables(m, begin
p[i in genset, t in timeset] >= 0 # Power generation variable
u[i in genset, t in timeset], Bin # Binary variable for on/off status
v[i in genset, t in timeset], Bin # startup variable
w[i in genset, t in timeset], Bin # shutdown variable
r_up[i in genset, t in timeset] >= 0 # upward reserve
r_dn[i in genset, t in timeset] >= 0 # downward reserve
end)
@constraint(m, MinGen[i in genset, t in timeset], p[i,t] - r_dn[i,t] >= Pmin[i] * u[i,t])
@constraint(m, MaxGen[i in genset, t in timeset],
p[i,t] + r_up[i,t] <= Pmax[i] * u[i,t]
- (Pmax[i] - SU[i]) * v[i,t]
- (Pmax[i] - SD[i]) * w[i,t]
)
@constraint(m, RampUp[i in genset, t in 2:T], p[i,t] + r_up[i,t] - p[i,t-1] <= RU[i])
@constraint(m, RampDown[i in genset, t in 2:T], p[i,t-1] - (p[i,t] - r_dn[i,t]) <= RD[i])
@constraint(m, MinUpTime[i in genset, t in TU[i]:T], sum(v[i,τ] for τ in t-TU[i]+1:t) <= u[i,t])
@constraint(m, MinDownTime[i in genset, t in TD[i]:T], sum(w[i,τ] for τ in t-TD[i]+1:t) <= 1 - u[i,t])
@constraint(m, Logic[i in genset, t in timeset], u[i,t] - (t > 1 ? u[i,t-1] : u0[i]) == v[i,t] - w[i,t])
@constraint(m, UpReserve[t in timeset], ㅌsum(r_up[i,t] for i in genset) >= R[t])
@constraint(m, DownReserve[t in timeset], sum(r_dn[i,t] for i in genset) >= R[t])
@constraint(m, LoadBalance[t in timeset], sum(p[i,t] for i in genset) == D[t])
# Objective function
@objective(m, Min,
sum(C1[i] * p[i,t] + CU[i] * v[i,t] + CD[i] * w[i,t] for i in genset, t in timeset))
optimize!(m)
#----------------------------------------------------------------------------------#
println("Termination status: ", termination_status(m))
println("Objective value: ", objective_value(m))
println("P: ", value.(P))
println("u: ", value.(u))
println("Gencost: ", value.(Gencost))
println("cU: ", value.(cU))
println("OC: ", value.(OC))