1 #------------------------------------------------------------------------------
2 # Copyright (C) 1996-2010 Power System Engineering Research Center (PSERC)
3 # Copyright (C) 2007-2010 Richard Lincoln
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #------------------------------------------------------------------------------
18 """ Defines solvers for AC power flow.
20 Based on runpf.m from MATPOWER by Ray Zimmerman, developed at PSERC Cornell.
21 See U{http://www.pserc.cornell.edu/matpower/} for more information.
24 #------------------------------------------------------------------------------
26 #------------------------------------------------------------------------------
31 from numpy
import array
, angle
, pi
, exp
, linalg
, multiply
, conj
, r_
, Inf
33 from scipy
.sparse
import hstack
, vstack
34 from scipy
.sparse
.linalg
import spsolve
, splu
36 from pylon
.case
import PQ
, PV
, REFERENCE
38 #------------------------------------------------------------------------------
40 #------------------------------------------------------------------------------
42 logger
= logging
.getLogger(__name__
)
44 #------------------------------------------------------------------------------
46 #------------------------------------------------------------------------------
51 #------------------------------------------------------------------------------
53 #------------------------------------------------------------------------------
55 class SlackBusError(Exception):
56 """ No single slack bus error. """
58 #------------------------------------------------------------------------------
60 #------------------------------------------------------------------------------
63 """ Defines a base class for AC power flow solvers.
65 Based on runpf.m from MATPOWER by Ray Zimmerman, developed at PSERC
66 Cornell. See U{http://www.pserc.cornell.edu/matpower/} for more info.
69 #--------------------------------------------------------------------------
71 #--------------------------------------------------------------------------
73 def __init__(self
, case
, qlimit
=False, tolerance
=1e-08, iter_max
=10,
78 #: Enforce Q limits on generators.
81 #: Convergence tolerance.
82 self
.tolerance
= tolerance
84 #: Maximum number of iterations.
85 self
.iter_max
= iter_max
87 #: Print progress information.
88 self
.verbose
= verbose
90 #--------------------------------------------------------------------------
92 #--------------------------------------------------------------------------
98 @return: Solution dictionary with the following keys:
99 - C{V} - final complex voltages
100 - C{converged} - boolean value indicating if the solver
102 - C{iterations} - the number of iterations performed
104 # Zero result attributes.
107 # Retrieve the contents of the case.
108 b
, l
, g
, _
, _
, _
, _
= self
._unpack
_case
(self
.case
)
110 # Update bus indexes.
111 self
.case
.index_buses(b
)
113 # Index buses accoding to type.
115 # _, pq, pv, pvpq = self._index_buses(b)
116 # except SlackBusError:
117 # logger.error("Swing bus required for DCPF.")
118 # return {"converged": False}
120 refs
, pq
, pv
, pvpq
= self
._index
_buses
(b
)
122 logger
.error("Swing bus required for DCPF.")
123 return {"converged": False}
128 # Build the vector of initial complex bus voltages.
129 V0
= self
._initial
_voltage
(b
, g
)
131 # Save index and angle of original reference bus.
134 # Varef0 = b[ref0].Va
135 # # List of buses at Q limits.
137 # # Qg of generators at Q limits.
138 # fixedQg = matrix(0.0, (g.size[0], 1))
142 # Build admittance matrices.
143 Ybus
, Yf
, Yt
= self
.case
.getYbus(b
, l
)
145 # Compute complex bus power injections (generation - load).
146 Sbus
= self
.case
.getSbus(b
)
148 # Run the power flow.
149 V
, converged
, i
= self
._run
_power
_flow
(Ybus
, Sbus
, V0
, pv
, pq
, pvpq
)
151 # Update case with solution.
152 self
.case
.pf_solution(Ybus
, Yf
, Yt
, V
)
154 # Enforce generator Q limits.
156 raise NotImplementedError
160 elapsed
= time() - t0
162 if converged
and self
.verbose
:
163 logger
.info("AC power flow converged in %.3fs" % elapsed
)
165 return {"converged": converged
, "elapsed": elapsed
, "iterations": i
,
169 def _unpack_case(self
, case
):
170 """ Returns the contents of the case to be used in the OPF.
172 base_mva
= case
.base_mva
173 b
= case
.connected_buses
174 l
= case
.online_branches
175 g
= case
.online_generators
180 return b
, l
, g
, nb
, nl
, ng
, base_mva
183 def _index_buses(self
, buses
):
184 """ Set up indexing for updating v.
186 refs
= [bus
._i
for bus
in buses
if bus
.type == REFERENCE
]
188 # raise SlackBusError
189 pv
= [bus
._i
for bus
in buses
if bus
.type == PV
]
190 pq
= [bus
._i
for bus
in buses
if bus
.type == PQ
]
193 return refs
, pq
, pv
, pvpq
196 def _initial_voltage(self
, buses
, generators
):
197 """ Returns the initial vector of complex bus voltages.
199 The bus voltage vector contains the set point for generator
200 (including ref bus) buses, and the reference angle of the swing
201 bus, as well as an initial guess for remaining magnitudes and
204 Vm
= array([bus
.v_magnitude
for bus
in buses
])
206 # Initial bus voltage angles in radians.
207 Va
= array([bus
.v_angle
* (pi
/ 180.0) for bus
in buses
])
209 V
= Vm
* exp(1j
* Va
)
211 # Get generator set points.
214 V
[i
] = g
.v_magnitude
/ abs(V
[i
]) * V
[i
]
219 def _run_power_flow(self
, Ybus
, Sbus
, V0
):
220 """ Override this method in subclasses.
222 raise NotImplementedError
224 #------------------------------------------------------------------------------
226 #------------------------------------------------------------------------------
228 class NewtonPF(_ACPF
):
229 """ Solves the power flow using full Newton's method.
231 Based on newtonpf.m from MATPOWER by Ray Zimmerman, developed at PSERC
232 Cornell. See U{http://www.pserc.cornell.edu/matpower/} for more info.
235 def _run_power_flow(self
, Ybus
, Sbus
, V
, pv
, pq
, pvpq
, **kw_args
):
236 """ Solves the power flow using a full Newton's method.
241 # Initial evaluation of F(x0)...
242 F
= self
._evaluate
_function
(Ybus
, V
, Sbus
, pv
, pq
)
243 # ...and convergency check.
244 converged
= self
._check
_convergence
(F
)
246 # Perform Newton iterations.
248 while (not converged
) and (i
< self
.iter_max
):
249 V
, Vm
, Va
= self
._one
_iteration
(F
, Ybus
, V
, Vm
, Va
, pv
, pq
, pvpq
)
250 F
= self
._evaluate
_function
(Ybus
, V
, Sbus
, pv
, pq
)
251 converged
= self
._check
_convergence
(F
)
256 logger
.info("Newton's method power flow converged in %d "
259 logger
.error("Newton's method power flow did not converge in %d "
262 return V
, converged
, i
265 def _one_iteration(self
, F
, Ybus
, V
, Vm
, Va
, pv
, pq
, pvpq
):
266 """ Performs one Newton iteration.
268 J
= self
._build
_jacobian
(Ybus
, V
, pv
, pq
, pvpq
)
271 dx
= -1 * spsolve(J
, F
)
272 # dx = -1 * linalg.lstsq(J.todense(), F)[0]
274 # Update voltage vector.
278 Va
[pv
] = Va
[pv
] + dx
[range(npv
)]
280 Va
[pq
] = Va
[pq
] + dx
[range(npv
, npv
+ npq
)]
281 Vm
[pq
] = Vm
[pq
] + dx
[range(npv
+ npq
, npv
+ npq
+ npq
)]
283 V
= Vm
* exp(1j
* Va
)
284 Vm
= abs(V
) # Avoid wrapped round negative Vm.
289 #--------------------------------------------------------------------------
291 #--------------------------------------------------------------------------
293 def _evaluate_function(self
, Ybus
, V
, Sbus
, pv
, pq
):
296 mis
= multiply(V
, conj(Ybus
* V
)) - Sbus
298 F
= r_
[mis
[pv
].real
, mis
[pq
].real
, mis
[pq
].imag
]
302 #--------------------------------------------------------------------------
304 #--------------------------------------------------------------------------
306 def _check_convergence(self
, F
):
307 """ Checks if the solution has converged to within the specified
310 normF
= linalg
.norm(F
, Inf
)
312 if normF
< self
.tolerance
:
317 logger
.info("Difference: %.3f" % (normF
- self
.tolerance
))
321 #--------------------------------------------------------------------------
323 #--------------------------------------------------------------------------
325 def _build_jacobian(self
, Ybus
, V
, pv
, pq
, pvpq
):
326 """ Returns the Jacobian matrix.
328 pq_col
= [[i
] for i
in pq
]
329 pvpq_col
= [[i
] for i
in pvpq
]
331 dS_dVm
, dS_dVa
= self
.case
.dSbus_dV(Ybus
, V
)
333 J11
= dS_dVa
[pvpq_col
, pvpq
].real
335 J12
= dS_dVm
[pvpq_col
, pq
].real
336 J21
= dS_dVa
[pq_col
, pvpq
].imag
337 J22
= dS_dVm
[pq_col
, pq
].imag
346 #------------------------------------------------------------------------------
347 # "FastDecoupledPF" class:
348 #------------------------------------------------------------------------------
350 class FastDecoupledPF(_ACPF
):
351 """ Solves the power flow using fast decoupled method.
353 Based on fdpf.m from MATPOWER by Ray Zimmerman, developed at PSERC
354 Cornell. See U{http://www.pserc.cornell.edu/matpower/} for more info.
356 #--------------------------------------------------------------------------
357 # "object" interface:
358 #--------------------------------------------------------------------------
360 def __init__(self
, case
, qlimit
=False, tolerance
=1e-08, iter_max
=20,
361 verbose
=True, method
=XB
):
362 """ Initialises a new ACPF instance.
364 super(FastDecoupledPF
, self
).__init
__(case
, qlimit
, tolerance
,
366 #: Use XB or BX method?
370 def _run_power_flow(self
, Ybus
, Sbus
, V
, pv
, pq
, pvpq
):
371 """ Solves the power flow using a full Newton's method.
377 # FIXME: Do not repeat build for each Q limit loop.
378 Bp
, Bpp
= self
.case
.makeB(method
=self
.method
)
380 # Evaluate initial mismatch.
381 P
, Q
= self
._evaluate
_mismatch
(Ybus
, V
, Sbus
, pq
, pvpq
)
384 logger
.info("iteration max mismatch (p.u.) \n")
385 logger
.info("type # P Q \n")
386 logger
.info("---- ---- ----------- -----------\n")
389 converged
= self
._check
_convergence
(P
, Q
, i
, "P")
391 if converged
and self
.verbose
:
392 logger
.info("Converged!")
395 pq_col
= [[k
] for k
in pq
]
396 pvpq_col
= [[k
] for k
in pvpq
]
397 Bp
= Bp
[pvpq_col
, pvpq
].tocsc() # splu requires a CSC matrix
398 Bpp
= Bpp
[pq_col
, pq
].tocsc()
402 Bpp_solver
= splu(Bpp
)
403 # L = decomp.lu(Bp.todense())
404 # LU, P = decomp.lu_factor(Bp.todense())
406 # Perform Newton iterations.
407 while (not converged
) and (i
< self
.iter_max
):
409 # Perform P iteration, update Va.
410 V
, Vm
, Va
= self
._p
_iteration
(P
, Bp_solver
, Vm
, Va
, pvpq
)
413 P
, Q
= self
._evaluate
_mismatch
(Ybus
, V
, Sbus
, pq
, pvpq
)
415 converged
= self
._check
_convergence
(P
, Q
, i
, "P")
417 if self
.verbose
and converged
:
418 logger
.info("Fast-decoupled power flow converged in %d "
419 "P-iterations and %d Q-iterations." % (i
, i
- 1))
422 # Perform Q iteration, update Vm.
423 V
, Vm
, Va
= self
._q
_iteration
(Q
, Bpp_solver
, Vm
, Va
, pq
)
426 P
, Q
= self
._evaluate
_mismatch
(Ybus
, V
, Sbus
, pq
, pvpq
)
428 converged
= self
._check
_convergence
(P
, Q
, i
, "Q")
430 if self
.verbose
and converged
:
431 logger
.info("Fast-decoupled power flow converged in %d "
432 "P-iterations and %d Q-iterations." % (i
, i
))
435 if self
.verbose
and not converged
:
436 logger
.error("FDPF did not converge in %d iterations." % i
)
438 return V
, converged
, i
440 #--------------------------------------------------------------------------
442 #--------------------------------------------------------------------------
444 def _evaluate_mismatch(self
, Ybus
, V
, Sbus
, pq
, pvpq
):
445 """ Evaluates the mismatch.
447 mis
= (multiply(V
, conj(Ybus
* V
)) - Sbus
) / abs(V
)
454 #--------------------------------------------------------------------------
456 #--------------------------------------------------------------------------
458 def _check_convergence(self
, P
, Q
, i
, type):
459 """ Checks if the solution has converged to within the specified
462 normP
= linalg
.norm(P
, Inf
)
463 normQ
= linalg
.norm(Q
, Inf
)
466 logger
.info(" %s %3d %10.3e %10.3e" % (type,i
, normP
, normQ
))
468 if (normP
< self
.tolerance
) and (normQ
< self
.tolerance
):
475 #--------------------------------------------------------------------------
477 #--------------------------------------------------------------------------
479 def _p_iteration(self
, P
, Bp_solver
, Vm
, Va
, pvpq
):
480 """ Performs a P iteration, updates Va.
482 dVa
= -Bp_solver
.solve(P
)
485 Va
[pvpq
] = Va
[pvpq
] + dVa
486 V
= Vm
* exp(1j
* Va
)
490 #--------------------------------------------------------------------------
492 #--------------------------------------------------------------------------
494 def _q_iteration(self
, Q
, Bpp_solver
, Vm
, Va
, pq
):
495 """ Performs a Q iteration, updates Vm.
497 dVm
= -Bpp_solver
.solve(Q
)
500 Vm
[pq
] = Vm
[pq
] + dVm
501 V
= Vm
* exp(1j
* Va
)
505 # EOF -------------------------------------------------------------------------