Skip to content

Commit 275fb0e

Browse files
committed
generalized DCOPF formulation for standard IEEE test cases
1 parent eaab247 commit 275fb0e

File tree

10 files changed

+449
-0
lines changed

10 files changed

+449
-0
lines changed

DCOPF.py

+223
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
# import libraries
2+
import networkx
3+
from pyomo.environ import *
4+
import numpy as np
5+
import scipy.io as readmat
6+
import pdb
7+
import math
8+
import os
9+
10+
# get the current working directory
11+
dir_file = os.getcwd()
12+
13+
# testcase to use for the optimization
14+
test_case = '300'
15+
16+
# degree to radian conversion factor
17+
deg_to_rad = math.pi / 180
18+
19+
# extract the scenarios and their probabilities from external file
20+
scenario_file = dir_file + r"/scenario_files/failure_scenarios.mat"
21+
probability_file = dir_file + r"/scenario_files/scenario_probabilities_49.csv"
22+
23+
# create a concrete pyomo model
24+
model = ConcreteModel()
25+
26+
# load matpower test case in mat format
27+
matpower_mat_file = readmat.loadmat(dir_file + '/test_cases/case' + test_case + '.mat',
28+
struct_as_record=False,
29+
squeeze_me=True)
30+
# ensure that all the saved mat file are saved under workspace var name 'matpower_testcase'
31+
test_case = matpower_mat_file['matpower_testcase']
32+
33+
# load the bus, generation, and line data
34+
model.bus = test_case.bus
35+
model.line = test_case.branch
36+
model.gen = test_case.gen
37+
model.gen_cost = test_case.gencost
38+
39+
# initialize the parameters
40+
model.nEdges = len(model.line) # total number of edges
41+
model.nNodes = len(model.bus) # total number of nodes
42+
model.nGen = len(model.gen) # total number of generators
43+
44+
# gen dataset also has specific MVA base; use that instead when updating this version
45+
model.Pbase = 100 # MVA base 100 MVA to convert the system to pu system
46+
47+
# let us assume that infinite capacity is equal to 100 GW
48+
Inf_transfer_Pmax = 10e6
49+
Pmax_line = 10e6/model.Pbase
50+
51+
# # Pmax, Gmax, and max load for bounds set
52+
# if max(model.line[:, 7]) == 0:
53+
# Pmax_line = Inf_transfer_Pmax / model.Pbase
54+
# else:
55+
# Pmax_line = max(model.line[:, 7]) / model.Pbase
56+
57+
Gmax = max(model.gen[:, 8]) / model.Pbase
58+
max_load = max(model.bus[:, 2]) / model.Pbase
59+
60+
# variable ranges
61+
model.x_ij = range(0, model.nEdges) # edges variable range
62+
model.b_i = range(0, model.nNodes) # buses variable range
63+
64+
###############################################################################################################
65+
####################################### Variables #############################################################
66+
###############################################################################################################
67+
68+
# declaring pyomo variables
69+
70+
# first we declare steady state variables i.e. power flow needs to be maintained while in the steady state
71+
# since these should be the same for all scenarios, they are first stage variables
72+
# they have ss at the end for representation
73+
74+
# although bounds are mentioned here to maintain the standard, they will be redefined as per gen bus
75+
model.bus_gen_ss = Var(model.b_i, bounds=(0, Gmax), within=Reals, initialize=0) # bus generation variable
76+
model.Pij_ss = Var(model.x_ij, bounds=(-Pmax_line, Pmax_line), within=Reals,
77+
initialize=0) # active power flowing through each lines
78+
model.theta_ss = Var(model.b_i, bounds=(-2 * math.pi, 2 * math.pi), within=Reals, initialize=0) # angle of each bus
79+
80+
# model.xij = Var(model.x_ij, bounds=(0,1), within=Binary, initialize=0) # connectivity of each line
81+
# model.load_shed = Var(model.b_i, bounds=(0, max_load), within=Reals) # real active power shed at each bus
82+
83+
84+
###############################################################################################################
85+
####################################### Constraints ###########################################################
86+
###############################################################################################################
87+
88+
# pyomo constraints
89+
# creates a list of constraints as placeholders
90+
#################### bus power balance constraints ############################
91+
model.power_balance = ConstraintList()
92+
93+
# bus data col 3: active power demand, col 5: shunt conductance
94+
for bus_num_idx in range(model.nNodes):
95+
bus_num = model.bus[bus_num_idx, 0]
96+
97+
# identify the list of generators connected to each bus
98+
gens = np.where(model.gen[:, 0] == bus_num)[0].tolist()
99+
to_bus_list = np.where(model.line[:, 1] == bus_num)[0].tolist()
100+
from_bus_list = np.where(model.line[:, 0] == bus_num)[0].tolist()
101+
102+
model.power_balance.add(sum(model.bus_gen_ss[np.where(model.bus[:, 0] == model.gen[gen_num, 0])[0][0]]
103+
for gen_num in gens) +
104+
sum(model.Pij_ss[to_bus] for to_bus in to_bus_list) -
105+
sum(model.Pij_ss[from_bus] for from_bus in from_bus_list) ==
106+
model.bus[bus_num_idx, 2] / model.Pbase + model.bus[bus_num_idx, 4] / model.Pbase)
107+
108+
################## generator power limit constraint ###########################
109+
model.gen_limit = ConstraintList()
110+
# generator should generate power between its min and max active power limit
111+
# col 9: PMAX and col 10: PMIN (Note: in Python number starts from 0)
112+
for gen_num in range(model.nGen):
113+
model.gen_limit.add(model.bus_gen_ss[np.where(model.bus[:, 0] == model.gen[gen_num, 0])[0][0]] <=
114+
model.gen[gen_num, 8] / model.Pbase)
115+
model.gen_limit.add(model.bus_gen_ss[np.where(model.bus[:, 0] == model.gen[gen_num, 0])[0][0]] >=
116+
model.gen[gen_num, 9] / model.Pbase)
117+
118+
# make sure non-generating bus do not generate anything
119+
for bus_num_idx in range(model.nNodes):
120+
bus_num = model.bus[bus_num_idx, 0]
121+
122+
if not np.any(np.equal(model.gen[:, 0], bus_num)):
123+
model.gen_limit.add(model.bus_gen_ss[bus_num_idx] == 0)
124+
125+
####################### active power flow constraint on each line ################
126+
model.power_flow = ConstraintList()
127+
'''
128+
Note: in Python number starts from 0
129+
130+
linedata:
131+
col 4: reactance (X)
132+
col 9: transformer tap ratio
133+
col 10: transformer phase shift (in degrees)
134+
135+
busdata:
136+
col 9: voltage angle (in degrees) -> this is a variable here so no need to use as parameter
137+
'''
138+
139+
for line_num in range(model.nEdges):
140+
141+
# MATPOWER keeps 0 for transmission lines without transformer
142+
# here we need to ensure tap ratio for transmission line is 1
143+
if model.line[line_num, 8] == 0:
144+
model.line[line_num, 8] = 1
145+
146+
reciprocal_term = 1 / (model.line[line_num, 3] * model.line[line_num, 8])
147+
model.power_flow.add(model.Pij_ss[line_num] ==
148+
reciprocal_term * (model.theta_ss[np.where(model.bus[:, 0] == model.line[line_num, 0])[0][0]] -
149+
model.theta_ss[np.where(model.bus[:, 0] == model.line[line_num, 1])[0][0]] -
150+
(model.line[line_num, 9] * deg_to_rad)))
151+
152+
################### thermal limit (MVA_limits) ############################
153+
# since the flow can be bi-directional, limits range from neg to positive value
154+
# col 6: max MVA limit of the line (0 means unlimited capacity)
155+
# this constraint tightens the -inf, inf bound set during variable initialization
156+
for line_num in range(model.nEdges):
157+
if model.line[line_num, 5] == 0:
158+
model.line[line_num, 5] = Pmax_line
159+
model.power_flow.add(model.Pij_ss[line_num] <= model.line[line_num, 5] / model.Pbase)
160+
model.power_flow.add(model.Pij_ss[line_num] >= - model.line[line_num, 5] / model.Pbase)
161+
162+
################### angle difference between two buses on each line ################
163+
model.angle_limit = ConstraintList()
164+
# from bus and to bus reference is obtained via line
165+
# col 12: min angle difference (degree), col 13: max angle difference (degree)
166+
for angle_num in range(model.nEdges):
167+
model.angle_limit.add((model.theta_ss[np.where(model.bus[:, 0] == model.line[angle_num, 0])[0][0]] -
168+
model.theta_ss[np.where(model.bus[:, 0] == model.line[angle_num, 1])[0][0]])
169+
<= model.line[angle_num, 12] * deg_to_rad)
170+
model.angle_limit.add((model.theta_ss[np.where(model.bus[:, 0] == model.line[angle_num, 0])[0][0]] -
171+
model.theta_ss[np.where(model.bus[:, 0] == model.line[angle_num, 1])[0][0]])
172+
>= model.line[angle_num, 11] * deg_to_rad)
173+
174+
# the angle can be anywhere from -2pi to 2pi hence we need to maintain 0 angle at reference (slack) bus
175+
176+
# identifying slack bus
177+
slack_bus = np.where(model.bus[:, 1] == 3)[0][0]
178+
179+
# ensure the angle at reference bus is 0
180+
model.angle_limit.add(model.theta_ss[slack_bus] == 0)
181+
182+
183+
# ###################### max load shedding ############################################
184+
# for load_bus_num in range(model.nNodes):
185+
# model.c.add(model.load_shed[load_bus_num] >= 0)
186+
# model.c.add(model.load_shed[load_bus_num] <= model.bus[load_bus_num, 2])
187+
188+
189+
############################# Overall Objective ###########################
190+
191+
def overall_objective(model):
192+
expr = 0
193+
expr = sum(model.gen_cost[gen_num, 4] * (model.bus_gen_ss[np.where(model.bus[:, 0] == model.gen[gen_num, 0])[0][0]]
194+
* model.Pbase) ** 2 +
195+
model.gen_cost[gen_num, 5] * (model.bus_gen_ss[np.where(model.bus[:, 0] == model.gen[gen_num, 0])[0][0]]
196+
* model.Pbase) +
197+
model.gen_cost[gen_num, 6] for gen_num in range(model.nGen))
198+
# expr = sum(model.load_shed[bus_num] for bus_num in range(model.nNodes))
199+
return expr
200+
201+
202+
model.min_gen_cost = Objective(rule=overall_objective, sense=minimize)
203+
204+
####################### Solve the economic dispatch problem ################
205+
206+
# create lp file for debugging the model
207+
model.write('check.lp', io_options={'symbolic_solver_labels': True})
208+
solver = SolverFactory('gurobi')
209+
results = solver.solve(model, tee=True)
210+
211+
# Observing the results
212+
213+
for k in range(0, model.nGen):
214+
print("G[%d] = %f" % (model.gen[k, 0], value(model.bus_gen_ss[np.where(model.bus[:, 0] ==
215+
model.gen[k, 0])[0][0]])))
216+
217+
sum_P = 0;
218+
for k in range(0, model.nEdges):
219+
print("Pij[%d] = %f" % (k + 1, value(model.Pij_ss[k])))
220+
sum_P = sum_P + value(model.Pij_ss[k])
221+
222+
for k in range(0, model.nNodes):
223+
print("theta[%d] = %f" % (k + 1, value(model.theta_ss[k]) * 1 / deg_to_rad))

power_system_test_cases/case118.mat

7.44 KB
Binary file not shown.

power_system_test_cases/case14.mat

1.31 KB
Binary file not shown.

power_system_test_cases/case300.mat

12.6 KB
Binary file not shown.

power_system_test_cases/case39.mat

2.49 KB
Binary file not shown.

test_models/14bus_text/bus_data.txt

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
1 3 0.0 0.0 0 0 1 1.06 0.00 0 1 1.06 0.94
2+
2 2 21.7 12.7 0 0 1 1.05 -4.98 0 1 1.06 0.94
3+
3 2 94.2 19.0 0 0 1 1.01 -12.72 0 1 1.06 0.94
4+
4 1 47.8 -3.9 0 0 1 1.02 -10.33 0 1 1.06 0.94
5+
5 1 7.6 1.6 0 0 1 1.02 -8.78 0 1 1.06 0.94
6+
6 2 11.2 7.5 0 0 1 1.07 -14.22 0 1 1.06 0.94
7+
7 1 0.0 0.0 0 0 1 1.06 -13.37 0 1 1.06 0.94
8+
8 2 0.0 0.0 0 0 1 1.09 -13.36 0 1 1.06 0.94
9+
9 1 29.5 16.6 0 19 1 1.06 -14.94 0 1 1.06 0.94
10+
10 1 9.0 5.8 0 0 1 1.05 -15.10 0 1 1.06 0.94
11+
11 1 3.5 1.8 0 0 1 1.06 -14.79 0 1 1.06 0.94
12+
12 1 6.1 1.6 0 0 1 1.06 -15.07 0 1 1.06 0.94
13+
13 1 13.5 5.8 0 0 1 1.05 -15.16 0 1 1.06 0.94
14+
14 1 14.9 5.0 0 0 1 1.04 -16.04 0 1 1.06 0.94

test_models/14bus_text/gen_cost.txt

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2 0 0 3 0.0430292599000000 20 0
2+
2 0 0 3 0.250000000000000 20 0
3+
2 0 0 3 0.0100000000000000 40 0
4+
2 0 0 3 0.0100000000000000 40 0
5+
2 0 0 3 0.0100000000000000 40 0

test_models/14bus_text/gen_data.txt

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
1 232 -16.9 10 0 1 100 1 332.4 0 0 0 0 0 0 0 0 0 0 0 0
2+
2 40 42.4 50 -40 1 100 1 140.0 0 0 0 0 0 0 0 0 0 0 0 0
3+
3 0 23.4 40 0 1 100 1 100.0 0 0 0 0 0 0 0 0 0 0 0 0
4+
6 0 12.2 24 -6 1 100 1 100.0 0 0 0 0 0 0 0 0 0 0 0 0
5+
8 0 17.4 24 -6 1 100 1 100.0 0 0 0 0 0 0 0 0 0 0 0 0

test_models/14bus_text/line_data.txt

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
1 2 0.01938 0.05917 0.0528 0 0 0 1 0 1 -360 360
2+
1 5 0.05403 0.22304 0.0492 0 0 0 1 0 1 -360 360
3+
2 3 0.04699 0.19797 0.0438 0 0 0 1 0 1 -360 360
4+
2 4 0.05811 0.17632 0.0340 0 0 0 1 0 1 -360 360
5+
2 5 0.05695 0.17388 0.0346 0 0 0 1 0 1 -360 360
6+
3 4 0.06701 0.17103 0.0128 0 0 0 1 0 1 -360 360
7+
4 5 0.01335 0.04211 0.0000 0 0 0 1 0 1 -360 360
8+
4 7 0.00000 0.20912 0.0000 0 0 0 0.978 0 1 -360 360
9+
4 9 0.00000 0.55618 0.0000 0 0 0 0.969 0 1 -360 360
10+
5 6 0.00000 0.25202 0.0000 0 0 0 0.932 0 1 -360 360
11+
6 11 0.09498 0.19890 0.0000 0 0 0 1 0 1 -360 360
12+
6 12 0.12291 0.25581 0.0000 0 0 0 1 0 1 -360 360
13+
6 13 0.06615 0.13027 0.0000 0 0 0 1 0 1 -360 360
14+
7 8 0.00000 0.17615 0.0000 0 0 0 1 0 1 -360 360
15+
7 9 0.00000 0.11001 0.0000 0 0 0 1 0 1 -360 360
16+
9 10 0.03181 0.08450 0.0000 0 0 0 1 0 1 -360 360
17+
9 14 0.12711 0.27038 0.0000 0 0 0 1 0 1 -360 360
18+
10 11 0.08205 0.19207 0.0000 0 0 0 1 0 1 -360 360
19+
12 13 0.22092 0.19988 0.0000 0 0 0 1 0 1 -360 360
20+
13 14 0.17093 0.34802 0.0000 0 0 0 1 0 1 -360 360

0 commit comments

Comments
 (0)