Power flow#
Power flow determines the steady-state bus voltages and the active and
reactive power flowing through a network for a specified operating point.
In pwrs, use runpf() for an AC power flow and rundcpf() for the linear
DC approximation.
This guide focuses on running a study, selecting an algorithm, and consuming the results. For the equations and numerical methods behind the calculation, see Power Flow in Power System Internals. For the input matrix formats, see Case data.
A complete AC power-flow workflow#
The following example loads a built-in case, increases its active and reactive demand by 5%, solves it quietly, checks convergence, and extracts the most commonly used results.
import pwrs as mp
from pwrs.core.idx_brch import F_BUS, PF, PT, QF, QT, T_BUS
from pwrs.core.idx_bus import BUS_I, PD, QD, VA, VM
# case9() returns a MatpowerCase. Its matrix fields are NumPy arrays.
mpc = mp.case9()
mpc.bus[:, PD] *= 1.05
mpc.bus[:, QD] *= 1.05
# Prefer the native Python configuration interface in new code.
mpopt = mp.MatpowerConfig()
mpopt.verbose = 0
mpopt.out.all = 0
result = mp.runpf(mpc, mpopt)
if not result.success:
raise RuntimeError("AC power flow did not converge")
bus = result.bus
branch = result.branch
bus_results = {
"bus": bus[:, BUS_I].astype(int),
"vm_pu": bus[:, VM],
"va_deg": bus[:, VA],
}
branch_results = {
"from_bus": branch[:, F_BUS].astype(int),
"to_bus": branch[:, T_BUS].astype(int),
"pf_mw": branch[:, PF],
"qf_mvar": branch[:, QF],
"pt_mw": branch[:, PT],
"qt_mvar": branch[:, QT],
}
print(
f"converged in {result.iterations} iterations "
f"and {result.et:.4f} seconds"
)
print(bus_results)
print(branch_results)
The pwrs.core.idx_* column constants are zero-based and can be used
directly as NumPy column indices. Using VM is safer and more readable than
embedding column number 7 directly in application code. Values stored in
identifier columns, such as BUS_I, remain MATPOWER bus numbers.
runpf() normalizes and deep-copies the case before solving it. The input
mpc therefore retains the operating point supplied by the caller, while
the returned PowerFlowResult contains the solved state. In particular, an
input branch matrix with the standard 13 columns is expanded in the result
to include PF, QF, PT, and QT.
Understanding the result#
The normal Python call returns a PowerFlowResult containing the original
case fields plus calculation status and solved values. Its fields use
attribute access and are statically typed.
Field |
Meaning |
|---|---|
|
|
|
Total solver iterations, including repeated solves used to enforce reactive-power limits. |
|
Elapsed solver time in seconds. |
|
Bus data updated with voltage magnitude |
|
Generator data updated with active power |
|
Branch data extended with from- and to-end active and reactive flows. |
|
Bookkeeping used to map internal indexing and out-of-service elements back to the external case. |
Always check success before treating the returned voltages and flows as a
valid operating point. Offline generators and branches are restored in the
external result; their calculated power columns are set to zero.
For compatibility with existing MATPOWER-style Python code, mapping access
such as result["bus"], result.get("order"), and result.to_dict() remains
available. New code should prefer result.bus and other attributes.
The relevant constants are defined in pwrs.core.idx_bus,
pwrs.core.idx_gen, and pwrs.core.idx_brch. The full table definitions are
available in the API Reference.
Choosing an AC algorithm#
The default algorithm is Newton’s method using power mismatch equations and polar voltage variables. It is the appropriate starting point for most meshed transmission-system studies.
|
Method |
Typical use |
|---|---|---|
|
Newton, power mismatch, polar voltage |
General-purpose default. |
|
Newton, power mismatch, Cartesian or hybrid voltage |
Alternative voltage representations for difficult cases or comparison studies. |
|
Newton, current mismatch, polar, Cartesian, or hybrid voltage |
Alternative balance formulations for numerical studies. |
|
Fast-decoupled XB or BX |
Lower-cost iterations when the decoupling assumptions suit the network. |
|
Gauss–Seidel |
Primarily compatibility, teaching, and small studies. |
|
Power, current, or admittance summation |
Radial networks only. |
Select an algorithm and tune its stopping criteria through
MatpowerConfig:
import pwrs as mp
mpopt = mp.MatpowerConfig()
mpopt.pf.alg = "FDXB"
mpopt.pf.tol = 1e-8
mpopt.pf.fd.max_it = 50
mpopt.verbose = 0
mpopt.out.all = 0
result = mp.runpf(mp.case30(), mpopt)
Newton methods use pf.nr.max_it; fast-decoupled, Gauss–Seidel, and radial
methods use pf.fd.max_it, pf.gs.max_it, and pf.radial.max_it,
respectively. pf.tol is the termination tolerance on the per-unit
mismatch.
For large Newton solves, pf.nr.lin_solver selects the linear-system
backend. Leaving it empty lets pwrs choose a default. Setting it to "KLU"
uses the optional nbklu package when available; otherwise pwrs warns and
falls back to the default solver.
Users migrating MATPOWER scripts can configure the same option with the compatible name/value interface:
mpopt = mp.mpoption("pf.alg", "FDXB", "verbose", 0, "out.all", 0)
Explicit Newton names such as NR-IC set the corresponding balance and
voltage representation. Non-Newton algorithms require power balance with
polar voltages; incompatible pf.current_balance or pf.v_cartesian
settings raise an error. See Power-flow solution algorithms for the mathematical and
implementation-level distinctions.
Enforcing generator reactive-power limits#
An ordinary AC power flow maintains the voltage setpoint at PV and reference
buses even if the resulting generator reactive power lies outside QMIN and
QMAX. Enable reactive-power limit enforcement when the study must account
for this operational constraint:
mpopt = mp.MatpowerConfig()
mpopt.pf.enforce_q_lims = 1
mpopt.verbose = 0
mpopt.out.all = 0
result = mp.runpf(mp.case30(), mpopt)
The available modes are:
0: do not enforce reactive-power limits (default).1: fix all currently violated generators at their limits and convert the corresponding buses to PQ simultaneously.2: apply only the largest remaining violation before solving again.
Enforcement can require multiple power-flow solves, so iterations is the
total across all passes. Once a voltage-controlling generator reaches a
reactive limit, its bus voltage magnitude is no longer guaranteed to remain
at the original setpoint. The solve can also become infeasible if no PV or
reference generator remains able to regulate voltage. Enforcement of a
reactive limit on a reference bus is not supported for systems with multiple
reference buses.
See Limits, post-processing, and results for the detailed calculation sequence.
DC power flow#
Use rundcpf() when the linear DC approximation is appropriate, for example
for screening, active-power transfer analysis, or initialization of another
study:
import pwrs as mp
from pwrs.core.idx_brch import F_BUS, PF, T_BUS
from pwrs.core.idx_bus import BUS_I, VA
mpopt = mp.MatpowerConfig()
mpopt.verbose = 0
mpopt.out.all = 0
result = mp.rundcpf(mp.case30(), mpopt)
if not result.success:
raise RuntimeError("DC power flow failed")
bus_angles = result.bus[:, [BUS_I, VA]]
active_flows = result.branch[:, [F_BUS, T_BUS, PF]]
print(bus_angles)
print(active_flows)
The DC model fixes voltage magnitudes at 1 p.u., omits reactive power, and returns zero reactive branch flows. Bus angles remain in degrees and active branch flows remain in MW in the external result. Because voltage magnitude, reactive-power balance, losses, and other AC effects are absent, a successful DC solve is not evidence that the same operating point is AC feasible.
Lower-level functions such as dcpf, makeBdc, and makePTDF are available
for specialized workflows; most applications should begin with rundcpf().
Controlling printed output#
verbose controls progress and convergence messages from the algorithms,
while the fields under out control the formatted result report. For a quiet
batch calculation, set both controls:
mpopt = mp.MatpowerConfig()
mpopt.verbose = 0
mpopt.out.all = 0
For an interactive diagnostic run, retain the normal report and increase verbosity:
mpopt = mp.MatpowerConfig()
mpopt.verbose = 2
mpopt.out.bus = 1
mpopt.out.branch = 1
runpf() also accepts fname for appending a formatted report,
solvedcase for saving a solved MATPOWER case, and nargout for emulating
MATLAB return signatures. These are compatibility interfaces; new Python
code should normally consume the returned structured result directly.
Troubleshooting convergence and input problems#
When a calculation fails or produces an unexpected result:
Check
result.successand rerun withverbose = 2before inspecting the numerical result.Verify the case has
baseMVA,bus,gen, andbranch, with valid bus numbers, an in-service reference generator, and a connected path for each energized island.Check initial
VM,VA, and generatorVGvalues, especially for a case assembled or modified programmatically.Retry with the default
NRalgorithm before tuning tolerances or maximum iteration counts. A looser tolerance can hide modeling errors and should not be the first remedy.If
pf.enforce_q_limsis enabled, inspectQG,QMIN,QMAX, and bus type conversion; the constrained problem may not have a feasible voltage- regulated solution.Use
PQSUM,ISUM, andYSUMonly for radial networks, and do not combine non-Newton methods with current-balance or Cartesian-voltage options.Treat ZIP-load warnings explicitly. Newton current-balance, Cartesian/hybrid Newton, and Gauss–Seidel configurations that do not support the requested ZIP model fall back to constant-power loads.
For diagnostics below the high-level workflow, the API Reference documents
bustypes, makeYbus, makeSbus, pfsoln, and the individual solver
functions. Their role in the full calculation is described in Power System
Internals rather than duplicated here.