-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathac_opf_powerModelsAnnex.jl
278 lines (218 loc) · 12.9 KB
/
ac_opf_powerModelsAnnex.jl
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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
#### AC Optimal Power Flow ####
# This file provides a pedagogical example of modeling the AC Optimal Power
# Flow problem using the Julia Mathematical Programming package (JuMP) and the
# PowerModels package for data parsing.
# This file can be run by calling `include("ac-opf.jl")` from the Julia REPL or
# by calling `julia ac-opf.jl` in Julia v1.
# Developed by Line Roald (@lroald) and Carleton Coffrin (@ccoffrin)
###############################################################################
# 0. Initialization
###############################################################################
# Load Julia Packages
#--------------------
using PowerModels
using Ipopt
using JuMP
using BilevelJuMP
using Gurobi
# Load System Data
# ----------------
network_list = ["base_case" "sole_gen" "high_risk"]
network = network_list[1]
powermodels_path = joinpath(dirname(pathof(PowerModels)), "..")
file_name = "case5_risk_$network.m"#"$(powermodels_path)/test/data/matpower/case5.m"
# note: change this string to modify the network data that will be loaded
# load the data file
data = PowerModels.parse_file(file_name)
# Add zeros to turn linear objective functions into quadratic ones
# so that additional parameter checks are not required
PowerModels.standardize_cost_terms!(data, order=2)
# Adds reasonable rate_a values to branches without them
PowerModels.calc_thermal_limits!(data)
# use build_ref to filter out inactive components
ref = PowerModels.build_ref(data)[:it][:pm][:nw][0]
# note: ref contains all the relevant system parameters needed to build the OPF model
# When we introduce constraints and variable bounds below, we use the parameters in ref.
################################################################################################################
# AW. This is the data that will be used to build the bilevel optimization problem from wildfire_nn_optimization
################################################################################################################
# Get the wildfire risk
eng = PowerModels.parse_file("case5_risk_$network.m")
risk = []
for i in 1:length(ref[:branch])
push!(risk, eng["branch"]["$i"]["power_risk"])
end
total_risk = sum(risk)
# Get the total load
loads = []
for i in keys(eng["load"])
push!(loads, eng["load"][i]["pd"])
end
D_p = sum(loads)
###############################################################################
# 1. Building the Optimal Power Flow Model
###############################################################################
# Initialize a JuMP Optimization Model
#-------------------------------------
#model = Model(Ipopt.Optimizer)
##############################################################################################################
# AW. Using BilevelJuMP to setup the 1st stage as the trade-off and the second stage as the conventional DCOPF
##############################################################################################################
model = BilevelModel(
Gurobi.Optimizer,
mode = BilevelJuMP.ProductMode()#FortunyAmatMcCarlMode(primal_big_M = 100, dual_big_M = 100)
)
#try to use the SOC ACOPF formulation in Powr MOdels
##############################################################################################################
# Define the upper level problems
@variable(Upper(model), line_status[1:length(data["branch"])], Bin)
@variable(Upper(model), total_load_shed >= 0)
@constraint(Upper(model), total_load_shed <= .2*D_p)
@objective(Upper(model), Min, sum(risk[i] * line_status[i] for i in 1:length(data["branch"]))/total_risk
)
# DCOPF Formulation
set_optimizer_attribute(model, "NonConvex", 2)
#set_optimizer_attribute(model, "print_level", 0)
# note: print_level changes the amount of solver information printed to the terminal
# Add Optimization and State Variables
# ------------------------------------
# Add voltage angles va for each bus
@variable(Lower(model), va[i in keys(ref[:bus])])
# note: [i in keys(ref[:bus])] adds one `va` variable for each bus in the network
# Add voltage angles vm for each bus
@variable(Lower(model), ref[:bus][i]["vmin"] <= vm[i in keys(ref[:bus])] <= ref[:bus][i]["vmax"], start=1.0)
# note: this vairable also includes the voltage magnitude limits and a starting value
# Add active power generation variable pg for each generator (including limits)
@variable(Lower(model), ref[:gen][i]["pmin"] <= pg[i in keys(ref[:gen])] <= ref[:gen][i]["pmax"])
# Add reactive power generation variable qg for each generator (including limits)
@variable(Lower(model), ref[:gen][i]["qmin"] <= qg[i in keys(ref[:gen])] <= ref[:gen][i]["qmax"])
# Add power flow variables p to represent the active power flow for each branch
@variable(Lower(model), -ref[:branch][l]["rate_a"] <= p[(l,i,j) in ref[:arcs]] <= ref[:branch][l]["rate_a"])
# Add power flow variables q to represent the reactive power flow for each branch
@variable(Lower(model), -ref[:branch][l]["rate_a"] <= q[(l,i,j) in ref[:arcs]] <= ref[:branch][l]["rate_a"])
# note: ref[:arcs] includes both the from (i,j) and the to (j,i) sides of a branch
# Add power flow variables p_dc to represent the active power flow for each HVDC line
@variable(Lower(model), p_dc[a in ref[:arcs_dc]])
# Add power flow variables q_dc to represent the reactive power flow at each HVDC terminal
@variable(Lower(model), q_dc[a in ref[:arcs_dc]])
for (l,dcline) in ref[:dcline]
f_idx = (l, dcline["f_bus"], dcline["t_bus"])
t_idx = (l, dcline["t_bus"], dcline["f_bus"])
JuMP.set_lower_bound(p_dc[f_idx], dcline["pminf"])
JuMP.set_upper_bound(p_dc[f_idx], dcline["pmaxf"])
JuMP.set_lower_bound(q_dc[f_idx], dcline["qminf"])
JuMP.set_upper_bound(q_dc[f_idx], dcline["qmaxf"])
JuMP.set_lower_bound(p_dc[t_idx], dcline["pmint"])
JuMP.set_upper_bound(p_dc[t_idx], dcline["pmaxt"])
JuMP.set_lower_bound(q_dc[f_idx], dcline["qmint"])
JuMP.set_upper_bound(q_dc[f_idx], dcline["qmaxt"])
end
# Add Objective Function
# ----------------------
# index representing which side the HVDC line is starting
from_idx = Dict(arc[1] => arc for arc in ref[:arcs_from_dc])
# Minimize the cost of active power generation and cost of HVDC line usage
# assumes costs are given as quadratic functions
@objective(Lower(model), Min,
sum(gen["cost"][1]*pg[i]^2 + gen["cost"][2]*pg[i] + gen["cost"][3] for (i,gen) in ref[:gen]) +
sum(dcline["cost"][1]*p_dc[from_idx[i]]^2 + dcline["cost"][2]*p_dc[from_idx[i]] + dcline["cost"][3] for (i,dcline) in ref[:dcline])
)
# Add Constraints
# ---------------
# Fix the voltage angle to zero at the reference bus
for (i,bus) in ref[:ref_buses]
@constraint(Lower(model), va[i] == 0)
end
# Nodal power balance constraints
for (i,bus) in ref[:bus]
# Build a list of the loads and shunt elements connected to the bus i
bus_loads = [ref[:load][l] for l in ref[:bus_loads][i]]
bus_shunts = [ref[:shunt][s] for s in ref[:bus_shunts][i]]
# Active power balance at node i
@constraint(Lower(model),
sum(p[a] for a in ref[:bus_arcs][i]) + # sum of active power flow on lines from bus i +
sum(p_dc[a_dc] for a_dc in ref[:bus_arcs_dc][i]) == # sum of active power flow on HVDC lines from bus i =
sum(pg[g] for g in ref[:bus_gens][i]) - # sum of active power generation at bus i -
sum(load["pd"] for load in bus_loads) - # sum of active load consumption at bus i -
sum(shunt["gs"] for shunt in bus_shunts)*vm[i]^2 # sum of active shunt element injections at bus i
)
# Reactive power balance at node i
@constraint(Lower(model),
sum(q[a] for a in ref[:bus_arcs][i]) + # sum of reactive power flow on lines from bus i +
sum(q_dc[a_dc] for a_dc in ref[:bus_arcs_dc][i]) == # sum of reactive power flow on HVDC lines from bus i =
sum(qg[g] for g in ref[:bus_gens][i]) - # sum of reactive power generation at bus i -
sum(load["qd"] for load in bus_loads) + # sum of reactive load consumption at bus i -
sum(shunt["bs"] for shunt in bus_shunts)*vm[i]^2 # sum of reactive shunt element injections at bus i
)
end
# Branch power flow physics and limit constraints
for (i,branch) in ref[:branch]
# Build the from variable id of the i-th branch, which is a tuple given by (branch id, from bus, to bus)
f_idx = (i, branch["f_bus"], branch["t_bus"])
# Build the to variable id of the i-th branch, which is a tuple given by (branch id, to bus, from bus)
t_idx = (i, branch["t_bus"], branch["f_bus"])
# note: it is necessary to distinguish between the from and to sides of a branch due to power losses
p_fr = p[f_idx] # p_fr is a reference to the optimization variable p[f_idx]
q_fr = q[f_idx] # q_fr is a reference to the optimization variable q[f_idx]
p_to = p[t_idx] # p_to is a reference to the optimization variable p[t_idx]
q_to = q[t_idx] # q_to is a reference to the optimization variable q[t_idx]
# note: adding constraints to p_fr is equivalent to adding constraints to p[f_idx], and so on
vm_fr = vm[branch["f_bus"]] # vm_fr is a reference to the optimization variable vm on the from side of the branch
vm_to = vm[branch["t_bus"]] # vm_to is a reference to the optimization variable vm on the to side of the branch
va_fr = va[branch["f_bus"]] # va_fr is a reference to the optimization variable va on the from side of the branch
va_to = va[branch["t_bus"]] # va_fr is a reference to the optimization variable va on the to side of the branch
# Compute the branch parameters and transformer ratios from the data
g, b = PowerModels.calc_branch_y(branch)
tr, ti = PowerModels.calc_branch_t(branch)
g_fr = branch["g_fr"]
b_fr = branch["b_fr"]
g_to = branch["g_to"]
b_to = branch["b_to"]
tm = branch["tap"]^2
# note: tap is assumed to be 1.0 on non-transformer branches
# AC Power Flow Constraints
#####
# go through each constraint and add each one to see where the problem is coming from
# look to see at this error as a problem with regular JuMP not specifically with the BilevelJuMP library
# dig through the source code to see what the error actually means
# run SOC ACOPF to see if it will run
# ask Sam to see if he has any ideas
#####
# From side of the branch flow
@constraint(Lower(model), p_fr == (g+g_fr)/tm*vm_fr^2 + (-g*tr+b*ti)/tm*(vm_fr*vm_to*cos(va_fr-va_to)) + (-b*tr-g*ti)/tm*(vm_fr*vm_to*sin(va_fr-va_to)) )
@constraint(Lower(model), q_fr == -(b+b_fr)/tm*vm_fr^2 - (-b*tr-g*ti)/tm*(vm_fr*vm_to*cos(va_fr-va_to)) + (-g*tr+b*ti)/tm*(vm_fr*vm_to*sin(va_fr-va_to)) )
# To side of the branch flow
@constraint(Lower(model), p_to == (g+g_to)*vm_to^2 + (-g*tr-b*ti)/tm*(vm_to*vm_fr*cos(va_to-va_fr)) + (-b*tr+g*ti)/tm*(vm_to*vm_fr*sin(va_to-va_fr)) )
@constraint(Lower(model), q_to == -(b+b_to)*vm_to^2 - (-b*tr+g*ti)/tm*(vm_to*vm_fr*cos(va_fr-va_to)) + (-g*tr-b*ti)/tm*(vm_to*vm_fr*sin(va_to-va_fr)) )
# Voltage angle difference limit
@constraint(Lower(model), va_fr - va_to <= branch["angmax"])
@constraint(Lower(model), va_fr - va_to >= branch["angmin"])
# Apparent power limit, from side and to side
@constraint(Lower(model), p_fr^2 + q_fr^2 <= branch["rate_a"]^2)
@constraint(Lower(model), p_to^2 + q_to^2 == branch["rate_a"]^2)
end
# HVDC line constraints
for (i,dcline) in ref[:dcline]
# Build the from variable id of the i-th HVDC line, which is a tuple given by (hvdc line id, from bus, to bus)
f_idx = (i, dcline["f_bus"], dcline["t_bus"])
# Build the to variable id of the i-th HVDC line, which is a tuple given by (hvdc line id, to bus, from bus)
t_idx = (i, dcline["t_bus"], dcline["f_bus"]) # index of the ith HVDC line which is a tuple given by (line number, to bus, from bus)
# note: it is necessary to distinguish between the from and to sides of a HVDC line due to power losses
# Constraint defining the power flow and losses over the HVDC line
@constraint(model, (1-dcline["loss1"])*p_dc[f_idx] + (p_dc[t_idx] - dcline["loss0"]) == 0)
end
###############################################################################
# 3. Solve the Optimal Power Flow Model and Review the Results
###############################################################################
# Solve the optimization problem
optimize!(model)
# Check that the solver terminated without an error
println("The solver termination status is $(termination_status(model))")
# Check the value of the objective function
cost = objective_value(model)
println("The cost of generation is $(cost).")
# Check the value of an optimization variable
# Example: Active power generated at generator 1
pg1 = value(pg[1])
println("The active power generated at generator 1 is $(pg1*ref[:baseMVA]) MW.")
# note: the optimization model is in per unit, so the baseMVA value is used to restore the physical units