-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathunit_commitment.py
More file actions
206 lines (172 loc) · 7 KB
/
unit_commitment.py
File metadata and controls
206 lines (172 loc) · 7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
"""
unit_commitment.py - Implementation of a simplified unit commitment model
with multiple solver options to avoid CBC issues
"""
import numpy as np
import pulp as plp
def solve_unit_commitment(buses, branches, generators, base_mva=100.0, time_periods=24, solver_name=None):
"""
Solve a simplified unit commitment problem with flexible solver options
Args:
buses (list): List of bus data
branches (list): List of branch data
generators (list): List of generator data
base_mva (float): Base MVA for the system
time_periods (int): Number of time periods to solve for
solver_name (str, optional): Specify solver to use ('CBC', 'GLPK', 'CPLEX', 'GUROBI', 'SCIP')
If None, will try available solvers in sequence
Returns:
dict: Unit commitment results
"""
# Create optimization problem
prob = plp.LpProblem("Unit_Commitment", plp.LpMinimize)
# Get total system load
total_load = sum(bus[2] for bus in buses)
# Define variables
# Generator commitment (binary)
gen_commit = {}
# Generator output (continuous)
gen_output = {}
for t in range(time_periods):
for i, gen in enumerate(generators):
gen_id = f"g{i+1}"
gen_commit[gen_id, t] = plp.LpVariable(f"u_{gen_id}_{t}", cat=plp.LpBinary)
gen_output[gen_id, t] = plp.LpVariable(f"p_{gen_id}_{t}",
lowBound=gen[8], # Pmin
upBound=gen[7], # Pmax
cat=plp.LpContinuous)
# Define objective function: minimize cost
objective = plp.LpAffineExpression()
for t in range(time_periods):
for i, gen in enumerate(generators):
gen_id = f"g{i+1}"
# Cost is a + b*P + c*P^2, we linearize it as a*u + b*P
cost_a = gen[10] # Fixed cost
cost_b = gen[11] # Linear cost
objective += cost_a * gen_commit[gen_id, t] + cost_b * gen_output[gen_id, t]
prob += objective
# Add constraints
# Power balance: generation = load
for t in range(time_periods):
# Simple model: use a load profile that varies over time
load_factor = 0.8 + 0.4 * np.sin(np.pi * t / 12) # Load profile with peak at t=6
current_load = total_load * load_factor
prob += plp.lpSum([gen_output[f"g{i+1}", t] for i in range(len(generators))]) == current_load, f"power_balance_{t}"
# Generator limits
for t in range(time_periods):
for i, gen in enumerate(generators):
gen_id = f"g{i+1}"
# Output must be between min and max when committed
prob += gen_output[gen_id, t] <= gen[7] * gen_commit[gen_id, t], f"max_output_{gen_id}_{t}"
prob += gen_output[gen_id, t] >= gen[8] * gen_commit[gen_id, t], f"min_output_{gen_id}_{t}"
# Solve the problem with specified or available solver
status = -1
solver_message = ""
# Try specified solver or go through alternatives
solver_options = []
if solver_name:
# Use only the specified solver
solver_options = [solver_name]
else:
# Try multiple solvers in order of preference
solver_options = ['CBC', 'GLPK', 'CPLEX', 'GUROBI', 'SCIP']
# Try solvers until one works
for solver in solver_options:
try:
print(f"Attempting to solve with {solver}...")
if solver == 'CBC':
solver_instance = plp.PULP_CBC_CMD(msg=False)
elif solver == 'GLPK':
solver_instance = plp.GLPK_CMD(msg=False)
elif solver == 'CPLEX':
solver_instance = plp.CPLEX_CMD(msg=False)
elif solver == 'GUROBI':
solver_instance = plp.GUROBI_CMD(msg=False)
elif solver == 'SCIP':
solver_instance = plp.SCIP_CMD(msg=False)
else:
continue # Skip unknown solver
status = prob.solve(solver_instance)
solver_message = f"Solved with {solver}"
break # Stop if a solver works
except Exception as e:
print(f"Solver {solver} failed: {str(e)}")
continue
# Check if any solver worked
if status == -1:
print("All solvers failed. Using simplified dispatch model instead...")
return solve_simplified_dispatch(buses, generators)
# Extract results
results = {
'status': plp.LpStatus[status],
'solver': solver_message,
'objective': plp.value(prob.objective),
'generator_schedule': {}
}
# Extract generator schedules
for i, gen in enumerate(generators):
gen_id = f"g{i+1}"
schedule = []
for t in range(time_periods):
schedule.append({
'period': t,
'commitment': plp.value(gen_commit[gen_id, t]),
'output_mw': plp.value(gen_output[gen_id, t])
})
results['generator_schedule'][gen_id] = schedule
return results
def solve_simplified_dispatch(buses, generators):
"""
A fallback simple economic dispatch model when unit commitment fails
Args:
buses (list): List of bus data
generators (list): List of generator data
Returns:
dict: Simplified dispatch results
"""
# Get total load
total_load = sum(bus[2] for bus in buses)
# Sort generators by cost (cheapest first)
sorted_gens = sorted(generators, key=lambda g: g[11]) # Sort by marginal cost
# Dispatch generators to meet load
dispatched = []
remaining_load = total_load
total_cost = 0
for i, gen in enumerate(sorted_gens):
gen_id = f"g{i+1}"
pmax = gen[7]
pmin = gen[8]
cost_a = gen[10] # Fixed cost
cost_b = gen[11] # Linear cost
# Determine output
if remaining_load <= 0:
output = 0
committed = 0
else:
committed = 1
output = min(pmax, max(pmin, remaining_load))
remaining_load -= output
total_cost += cost_a + cost_b * output
dispatched.append({
'gen_id': gen_id,
'output_mw': output,
'commitment': committed
})
# Create schedule format (same time period for all)
results = {
'status': 'Optimal (Simplified)',
'solver': 'Simplified Economic Dispatch',
'objective': total_cost,
'generator_schedule': {}
}
# Format results in the same structure as unit commitment
for gen in dispatched:
schedule = []
for t in range(24):
schedule.append({
'period': t,
'commitment': gen['commitment'],
'output_mw': gen['output_mw']
})
results['generator_schedule'][gen['gen_id']] = schedule
return results