1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """Runs a power flow.
18 """
19
20 from sys import stdout, stderr
21
22 from os.path import dirname, join
23
24 from time import time
25
26 from numpy import r_, c_, ix_, zeros, pi, ones, exp, argmax
27 from numpy import flatnonzero as find
28
29 from pypower.bustypes import bustypes
30 from pypower.ext2int import ext2int
31 from pypower.loadcase import loadcase
32 from pypower.ppoption import ppoption
33 from pypower.ppver import ppver
34 from pypower.makeBdc import makeBdc
35 from pypower.makeSbus import makeSbus
36 from pypower.dcpf import dcpf
37 from pypower.makeYbus import makeYbus
38 from pypower.newtonpf import newtonpf
39 from pypower.fdpf import fdpf
40 from pypower.gausspf import gausspf
41 from pypower.makeB import makeB
42 from pypower.pfsoln import pfsoln
43 from pypower.printpf import printpf
44 from pypower.savecase import savecase
45 from pypower.int2ext import int2ext
46
47 from pypower.idx_bus import PD, QD, VM, VA, GS, BUS_TYPE, PQ
48 from pypower.idx_brch import PF, PT, QF, QT
49 from pypower.idx_gen import PG, QG, VG, QMAX, QMIN, GEN_BUS, GEN_STATUS
50
51
52 -def runpf(casedata=None, ppopt=None, fname='', solvedcase=''):
53 """Runs a power flow.
54
55 Runs a power flow [full AC Newton's method by default] and optionally
56 returns the solved values in the data matrices, a flag which is C{True} if
57 the algorithm was successful in finding a solution, and the elapsed
58 time in seconds. All input arguments are optional. If C{casename} is
59 provided it specifies the name of the input data file or dict
60 containing the power flow data. The default value is 'case9'.
61
62 If the ppopt is provided it overrides the default PYPOWER options
63 vector and can be used to specify the solution algorithm and output
64 options among other things. If the 3rd argument is given the pretty
65 printed output will be appended to the file whose name is given in
66 C{fname}. If C{solvedcase} is specified the solved case will be written
67 to a case file in PYPOWER format with the specified name. If C{solvedcase}
68 ends with '.mat' it saves the case as a MAT-file otherwise it saves it
69 as a Python-file.
70
71 If the C{ENFORCE_Q_LIMS} options is set to C{True} [default is false] then
72 if any generator reactive power limit is violated after running the AC
73 power flow, the corresponding bus is converted to a PQ bus, with Qg at
74 the limit, and the case is re-run. The voltage magnitude at the bus
75 will deviate from the specified value in order to satisfy the reactive
76 power limit. If the reference bus is converted to PQ, the first
77 remaining PV bus will be used as the slack bus for the next iteration.
78 This may result in the real power output at this generator being
79 slightly off from the specified values.
80
81 Enforcing of generator Q limits inspired by contributions from Mu Lin,
82 Lincoln University, New Zealand (1/14/05).
83
84 @author: Ray Zimmerman (PSERC Cornell)
85 @author: Richard Lincoln
86 """
87
88 if casedata is None:
89 casedata = join(dirname(__file__), 'case9')
90 ppopt = ppoption(ppopt)
91
92
93 verbose = ppopt["VERBOSE"]
94 qlim = ppopt["ENFORCE_Q_LIMS"]
95 dc = ppopt["PF_DC"]
96
97
98 ppc = loadcase(casedata)
99
100
101 if ppc["branch"].shape[1] < QT:
102 ppc["branch"] = c_[ppc["branch"],
103 zeros((ppc["branch"].shape[0],
104 QT - ppc["branch"].shape[1] + 1))]
105
106
107 ppc = ext2int(ppc)
108 baseMVA, bus, gen, branch = \
109 ppc["baseMVA"], ppc["bus"], ppc["gen"], ppc["branch"]
110
111
112 ref, pv, pq = bustypes(bus, gen)
113
114
115 on = find(gen[:, GEN_STATUS] > 0)
116 gbus = gen[on, GEN_BUS].astype(int)
117
118
119 t0 = time()
120 if verbose > 0:
121 v = ppver('all')
122 stdout.write('PYPOWER Version %s, %s' % (v["Version"], v["Date"]))
123
124 if dc:
125 if verbose:
126 stdout.write(' -- DC Power Flow\n')
127
128
129 Va0 = bus[:, VA] * (pi / 180)
130
131
132 B, Bf, Pbusinj, Pfinj = makeBdc(baseMVA, bus, branch)
133
134
135
136 Pbus = makeSbus(baseMVA, bus, gen).real - Pbusinj - bus[:, GS] / baseMVA
137
138
139 Va = dcpf(B, Pbus, Va0, ref, pv, pq)
140
141
142 branch[:, [QF, QT]] = zeros((branch.shape[0], 2))
143 branch[:, PF] = (Bf * Va + Pfinj) * baseMVA
144 branch[:, PT] = -branch[:, PF]
145 bus[:, VM] = ones(bus.shape[0])
146 bus[:, VA] = Va * (180 / pi)
147
148
149
150 refgen = find(gbus == ref)
151 gen[on[refgen[0]], PG] = gen[on[refgen[0]], PG] + (B[ref, :] * Va - Pbus[ref]) * baseMVA
152
153 success = 1
154 else:
155 if verbose > 0:
156 stdout.write(' -- AC Power Flow ')
157
158
159
160 V0 = bus[:, VM] * exp(1j * pi/180 * bus[:, VA])
161 V0[gbus] = gen[on, VG] / abs(V0[gbus]) * V0[gbus]
162
163 if qlim:
164 ref0 = ref
165 Varef0 = bus[ref0, VA]
166 limited = []
167 fixedQg = zeros(gen.shape[0])
168
169 repeat = True
170 while repeat:
171
172 Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch)
173
174
175 Sbus = makeSbus(baseMVA, bus, gen)
176
177
178 alg = ppopt["PF_ALG"]
179 if alg == 1:
180 V, success, _ = newtonpf(Ybus, Sbus, V0, ref, pv, pq, ppopt)
181 elif alg == 2 or alg == 3:
182 Bp, Bpp = makeB(baseMVA, bus, branch, alg)
183 V, success, _ = fdpf(Ybus, Sbus, V0, Bp, Bpp, ref, pv, pq, ppopt)
184 elif alg == 4:
185 V, success, _ = gausspf(Ybus, Sbus, V0, ref, pv, pq, ppopt)
186 else:
187 stderr.write('Only Newton''s method, fast-decoupled, and '
188 'Gauss-Seidel power flow algorithms currently '
189 'implemented.\n')
190
191
192 bus, gen, branch = pfsoln(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V, ref, pv, pq)
193
194 if qlim:
195
196 mx = find( gen[:, GEN_STATUS] > 0 & gen[:, QG] > gen[:, QMAX] )
197 mn = find( gen[:, GEN_STATUS] > 0 & gen[:, QG] < gen[:, QMIN] )
198
199 if len(mx) > 0 or len(mn) > 0:
200 if len(pv):
201 if verbose:
202 if len(mx) > 0:
203 print 'Gen %d [only one left] exceeds upper Q limit : INFEASIBLE PROBLEM\n' % mx
204 else:
205 print 'Gen %d [only one left] exceeds lower Q limit : INFEASIBLE PROBLEM\n' % mn
206
207 success = 0
208 break
209
210
211 if qlim == 2:
212 k = argmax(r_[gen[mx, QG] - gen[mx, QMAX],
213 gen[mn, QMIN] - gen[mn, QG]])
214 if k > len(mx):
215 mn = mn[k - len(mx)]
216 mx = []
217 else:
218 mx = mx[k]
219 mn = []
220
221 if verbose and len(mx) > 0:
222 print 'Gen %d at upper Q limit, converting to PQ bus\n' % mx
223
224 if verbose and len(mn) > 0:
225 print 'Gen %d at lower Q limit, converting to PQ bus\n' % mn
226
227
228 fixedQg[mx] = gen[mx, QMAX]
229 fixedQg[mn] = gen[mn, QMIN]
230 mx = r_[mx, mn]
231
232
233 gen[mx, QG] = fixedQg[mx]
234 gen[mx, GEN_STATUS] = 0
235 for i in range(mx):
236 bi = gen[mx[i], GEN_BUS]
237 bus[bi, [PD, QD]] = (bus[bi, [PD, QD]] - gen[mx[i], [PG, QG]])
238
239 bus[gen[mx, GEN_BUS], BUS_TYPE] = PQ
240
241
242 ref_temp = ref
243 ref, pv, pq = bustypes(bus, gen)
244 if verbose and ref != ref_temp:
245 print 'Bus %d is new slack bus\n' % ref
246
247 limited = r_[limited, mx]
248 else:
249 repeat = 0
250 else:
251 repeat = 0
252
253 if qlim and len(limited) > 0:
254
255 gen[limited, QG] = fixedQg[limited]
256 for i in range(limited):
257 bi = gen[limited[i], GEN_BUS]
258 bus[bi, [PD, QD]] = bus[bi, [PD, QD]] + gen[limited[i], [PG, QG]]
259
260 gen[limited, GEN_STATUS] = 1
261 if ref != ref0:
262
263 bus[:, VA] = bus[:, VA] - bus[ref0, VA] + Varef0
264
265 ppc["et"] = time() - t0
266 ppc["success"] = success
267
268
269
270 ppc["bus"], ppc["gen"], ppc["branch"] = bus, gen, branch
271 results = int2ext(ppc)
272
273
274 if len(results["order"]["gen"]["status"]["off"]) > 0:
275 results["gen"][ix_(results["order"]["gen"]["status"]["off"], [PG, QG])] = 0
276
277 if len(results["order"]["branch"]["status"]["off"]) > 0:
278 results["branch"][ix_(results["order"]["branch"]["status"]["off"], [PF, QF, PT, QT])] = 0
279
280 if fname:
281 fd = None
282 try:
283 fd = open(fname, "wb")
284 except Exception, detail:
285 stderr.write("Error opening %s: %s.\n" % (fname, detail))
286 finally:
287 if fd is not None:
288 printpf(results, fd, ppopt)
289 fd.close()
290
291 printpf(results, stdout, ppopt)
292
293
294 if solvedcase:
295 savecase(solvedcase, results)
296
297 return results, success
298
299
300 if __name__ == '__main__':
301 runpf()
302