Reactive Power Limits (Q-Limits) and PV→PQ Switching
Overview
Sparlectra supports per-bus reactive power limits for generators (PV buses). When a PV bus violates its Q-limits, it is automatically converted to a PQ bus during Newton-Raphson iteration (PV→PQ switching). Events are logged, and optionally hysteresis and a "cooldown" period can be used for later switching back.
Data Structures in Net
The Net type stores all Q-limit-related data:
struct Net
name::String
baseMVA::Float64
slackVec::Vector{Int}
vmin_pu::Float64
vmax_pu::Float64
nodeVec::Vector{Node}
linesAC::Vector{ACLineSegment}
trafos::Vector{PowerTransformer}
branchVec::Vector{Branch}
prosumpsVec::Vector{ProSumer}
shuntVec::Vector{Shunt}
busDict::Dict{String,Int}
busOrigIdxDict::Dict{Int,Int}
totalLosses::Vector{Tuple{Float64,Float64}}
totalBusPower::Vector{Tuple{Float64,Float64}}
_locked::Bool
shuntDict::Dict{Int,Int}
isoNodes::Vector{Int}
qLimitLog::Vector{Any} # chronological log of Q-limit events
cooldown_iters::Int # min. iterations between PV/PQ state changes
q_hyst_pu::Float64 # hysteresis band in p.u. for Q-backtracking
qmin_pu::Vector{Float64} # per-bus Qmin (p.u.)
qmax_pu::Vector{Float64} # per-bus Qmax (p.u.)
qLimitEvents::Dict{Int,Symbol} # BusIdx -> :min | :max (last PV→PQ change)
endTypical source of these limits:
- Q-limits come from generators / prosumers (e.g.
qMin,qMax), are converted to p.u. and aggregated per bus. - A helper like
setQLimits!/buildQLimits!pre-fillsnet.qmin_puandnet.qmax_pubefore the power flow.
Logging Q-Limit Hits
Q-limit events are represented by QLimitEvent and are stored in net.qLimitLog:
Base.@kwdef struct QLimitEvent
iter::Int
bus::Int
side::Symbol # :min | :max
endSupport functions:
logQLimitHit!(net, iter, bus, side)- Appends a
QLimitEventtonet.qLimitLog - Updates
net.qLimitEvents[bus] = side
- Appends a
lastQLimitIter(net, bus)- Returns the last iteration index in which
bushit a Q-limit.
- Returns the last iteration index in which
resetQLimitLog!(net)- Clears both
qLimitLogandqLimitEvents.
- Clears both
Human-readable printout:
printQLimitLog(net; sort_by = :iter, io = stdout)Example:
──────────────────────────────────
Iteration │ Bus │ Side
──────────────────────────────────
3 │ 5 │ min
5 │ 3 │ max
──────────────────────────────────
Total events: 2A convenience function for tests:
pv_hit_q_limit(net, ["B3", "B10"])returns true if any of the listed PV buses appears in net.qLimitEvents.
Conceptual Algorithm
During a Newton-Raphson calculation (e.g., calcNewtonRaphson_withPVIdentity! or the rectangular variant), the Q-limit logic works at its core as follows:
Solve one NR step and obtain updated bus voltages
Vand bus powersS = P + jQ.Compute actual generator Q per PV bus (from
Sor from bus data).Check limits per bus index
b:Let
Q_bbe the current reactive injection at busb.Let
[Qmin_b, Qmax_b]be the limits fromnet.qmin_puandnet.qmax_pu.If
\[Q_b < Qmin_b \quad \text{or} \quad Q_b > Qmax_b\]
then the PV bus has hit a limit.
Clip Q and switch bus type:
Set the generator’s
Qto the violated limit:Q_b := Qmin_borQ_b := Qmax_b.Change the bus type from PV to PQ:
setBusType!(net, b, "PQ")Log the event:
logQLimitHit!(net, iter, b, :min) # or :max
Optional: PV→PQ→PV re-enable (hysteresis + cooldown)
If
net.q_hyst_pu > 0andnet.cooldown_iters > 0, a bus may be switched back from PQ to PV only if:Its Q is now well inside the interval with margin:
\[Qmin_b + q_\mathrm{hyst} < Q_b < Qmax_b - q_\mathrm{hyst}\]
And sufficiently many Newton iterations have passed since the last limit hit:
\[\text{iter} - \text{lastQLimitIter}(b) \ge \text{cooldown\_iters}\]
In many applications, switching back within the same power flow calculation is intentionally disabled (q_hyst_pu = 0.0, cooldown_iters = 0).
- Repeat with the updated bus types and Q-values in the next NR iteration.
Interaction with the Rectangular Solver
The rectangular solver uses a separate bus_types vector with values :PQ, :PV, :Slack. When a PV bus hits its Q-limit, the conceptual solver action is the same as in the polar formulation: from the next iteration onward the bus is treated as PQ, so the residual switches from (ΔP, ΔV) to (ΔP, ΔQ).
bus_types[b] = :PQThis means the Q-limit logic changes the equation set used by the Newton step, not only a post-processing label.
For the numerical solver details behind this equation system, see Solver Guide.