The Sparlectra workshop tour
Note: This workshop was created with AI assistance and is reviewed and curated by the maintainer; it is not a fully machine-generated text.
This tour is the FIRST HALF of the Sparlectra workshop: install Sparlectra.jl once, warm the compiler up once, and then climb from the very first bus to the solver's control features in one session. The ADVANCED tour continues with the Expert and Beyond tiers (remote voltage control, HVDC, state estimation, FACTS, N-1, threads); the focused single-topic notebooks go deeper on individual chapters.
After the warm-up (compilation happens there, everything after is fast) the chapters climb three tiers:
Newcomer
- Your first network, built step by step
Beginner
- Working with the model: trust, switching, editing, Q-limits
Advanced
- Slack types and short-circuit currents
- Transformer tap control (OLTC)
- Voltage-dependent reactive power, Q(U)
Note: On Google Colab the install cell takes a few minutes on a fresh session (package download and precompilation). Colab's Julia version may change over time; this notebook targets Julia ≥ 1.12.
Warm-up and shared helpers
Julia compiles each function on first use. This one cell warms the paths the chapters exercise, so nothing stalls mid-tour: the Newton-Raphson solver and the IEC 60909 short circuit (chapter 3, Example 3.4). The using clauses and the small helpers of the whole tour live here too, collected up top so they cannot be missed.
using Sparlectra
using Random
# solve helper used by all chapters (25 iterations, tolerance 1e-8)
function solve!(net; kwargs...)
etime = @elapsed begin
ite, erg = runpf!(net, 25, 1e-8, 0; kwargs...)
end
erg == 0 || error("Power flow did not converge (status = $erg)")
calcNetLosses!(net)
return etime, ite
end
# tiny warm-up net: a grid connection WITH declared short-circuit data
wnet = Net(name = "warmup", baseMVA = 100.0)
addBus!(net = wnet, busName = "A", vn_kV = 110.0)
addBus!(net = wnet, busName = "B", vn_kV = 110.0)
addExternalGrid!(net = wnet, busName = "A", vm_pu = 1.0, sk_max_MVA = 2000.0, sk_min_MVA = 1500.0, rx_max = 0.1, internal_impedance = false)
addProsumer!(net = wnet, busName = "B", type = "ENERGYCONSUMER", p = 10.0, q = 3.0)
addPIModelACLine!(net = wnet, fromBus = "A", toBus = "B", r_pu = 0.01, x_pu = 0.08, b_pu = 0.0, status = 1)
t_first = @elapsed runpf!(wnet, 10, 1e-8, 0)
t_second = @elapsed runpf!(wnet, 10, 1e-8, 0)
println("power flow : first solve ", round(t_first; digits = 2), " s (compiles), second ", round(t_second * 1000; digits = 2), " ms")
t_sc = @elapsed runShortCircuit!(wnet; case = :max)
println("short circuit : ", round(t_sc; digits = 2), " s, everything warm")power flow : first solve 0.0 s (compiles), second 0.16 ms
short circuit : 0.0 s, everything warmPart I: Newcomer
Chapter 1: your first network, built step by step
Example 1.1: a 7-bus ring, built and solved. No input files, no configuration: a complete 110 kV network from scratch, validated, solved, and read. The network is seven buses in a ring with two cross-connections; B1 carries the grid connection:
(slack)
B1 ---- B2 ---- B3 ---- B4
| \ / |
| \ / |
| \/ | diagonals: B2-B5 and B3-B6
| /\ |
B7 ---- B6 ---- B5 ------+Every Sparlectra model starts from a Net object; baseMVA is the system base power for all per-unit conversions. addBus! creates the electrical nodes (vn_kV nominal voltage, vm_pu/va_deg the solver's starting voltage). Note what is NOT declared here: the operational bus type (slack / PV / PQ) is derived later from the devices attached to each bus.
net1 = Net(name = "tour_first_pf", baseMVA = 100.0)
addBus!(net = net1, busName = "B1", vn_kV = 110.0, vm_pu = 1.02, va_deg = 0.0)
for i in 2:7
addBus!(net = net1, busName = "B$(i)", vn_kV = 110.0, vm_pu = 1.0, va_deg = 0.0)
endaddPIModelACLine! connects buses with a line as a pi-equivalent branch in per-unit (r_pu, x_pu, and b_pu for the total charging):
addPIModelACLine!(net = net1, fromBus = "B1", toBus = "B2", r_pu = 0.010, x_pu = 0.080, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net1, fromBus = "B2", toBus = "B3", r_pu = 0.011, x_pu = 0.085, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net1, fromBus = "B3", toBus = "B4", r_pu = 0.012, x_pu = 0.090, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net1, fromBus = "B4", toBus = "B5", r_pu = 0.010, x_pu = 0.080, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net1, fromBus = "B5", toBus = "B6", r_pu = 0.011, x_pu = 0.085, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net1, fromBus = "B6", toBus = "B7", r_pu = 0.012, x_pu = 0.090, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net1, fromBus = "B7", toBus = "B1", r_pu = 0.010, x_pu = 0.080, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net1, fromBus = "B2", toBus = "B5", r_pu = 0.009, x_pu = 0.070, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net1, fromBus = "B3", toBus = "B6", r_pu = 0.009, x_pu = 0.070, b_pu = 0.0, status = 1)9-element Vector{Branch}:
Branch( Component(ID=#B_ACL_110_1_2#1, Name=B_ACL_110_1_2, Typ=BranchC, Vn=110.0, From_bus=1, To_bus=2, ), branchIdx: 1, fromBus: 1, toBus: 2, r_pu: 0.01, x_pu: 0.08, b_pu: 0.0, g_pu: 0.0, ratio: 0.0, angle: 0.0, status: 1, tap_ratio: 1.0, phase_shift_deg: 0.0, )
Branch( Component(ID=#B_ACL_110_2_3#2, Name=B_ACL_110_2_3, Typ=BranchC, Vn=110.0, From_bus=2, To_bus=3, ), branchIdx: 2, fromBus: 2, toBus: 3, r_pu: 0.011, x_pu: 0.085, b_pu: 0.0, g_pu: 0.0, ratio: 0.0, angle: 0.0, status: 1, tap_ratio: 1.0, phase_shift_deg: 0.0, )
Branch( Component(ID=#B_ACL_110_3_4#3, Name=B_ACL_110_3_4, Typ=BranchC, Vn=110.0, From_bus=3, To_bus=4, ), branchIdx: 3, fromBus: 3, toBus: 4, r_pu: 0.012, x_pu: 0.09, b_pu: 0.0, g_pu: 0.0, ratio: 0.0, angle: 0.0, status: 1, tap_ratio: 1.0, phase_shift_deg: 0.0, )
Branch( Component(ID=#B_ACL_110_4_5#4, Name=B_ACL_110_4_5, Typ=BranchC, Vn=110.0, From_bus=4, To_bus=5, ), branchIdx: 4, fromBus: 4, toBus: 5, r_pu: 0.01, x_pu: 0.08, b_pu: 0.0, g_pu: 0.0, ratio: 0.0, angle: 0.0, status: 1, tap_ratio: 1.0, phase_shift_deg: 0.0, )
Branch( Component(ID=#B_ACL_110_5_6#5, Name=B_ACL_110_5_6, Typ=BranchC, Vn=110.0, From_bus=5, To_bus=6, ), branchIdx: 5, fromBus: 5, toBus: 6, r_pu: 0.011, x_pu: 0.085, b_pu: 0.0, g_pu: 0.0, ratio: 0.0, angle: 0.0, status: 1, tap_ratio: 1.0, phase_shift_deg: 0.0, )
Branch( Component(ID=#B_ACL_110_6_7#6, Name=B_ACL_110_6_7, Typ=BranchC, Vn=110.0, From_bus=6, To_bus=7, ), branchIdx: 6, fromBus: 6, toBus: 7, r_pu: 0.012, x_pu: 0.09, b_pu: 0.0, g_pu: 0.0, ratio: 0.0, angle: 0.0, status: 1, tap_ratio: 1.0, phase_shift_deg: 0.0, )
Branch( Component(ID=#B_ACL_110_7_1#7, Name=B_ACL_110_7_1, Typ=BranchC, Vn=110.0, From_bus=7, To_bus=1, ), branchIdx: 7, fromBus: 7, toBus: 1, r_pu: 0.01, x_pu: 0.08, b_pu: 0.0, g_pu: 0.0, ratio: 0.0, angle: 0.0, status: 1, tap_ratio: 1.0, phase_shift_deg: 0.0, )
Branch( Component(ID=#B_ACL_110_2_5#8, Name=B_ACL_110_2_5, Typ=BranchC, Vn=110.0, From_bus=2, To_bus=5, ), branchIdx: 8, fromBus: 2, toBus: 5, r_pu: 0.009, x_pu: 0.07, b_pu: 0.0, g_pu: 0.0, ratio: 0.0, angle: 0.0, status: 1, tap_ratio: 1.0, phase_shift_deg: 0.0, )
Branch( Component(ID=#B_ACL_110_3_6#9, Name=B_ACL_110_3_6, Typ=BranchC, Vn=110.0, From_bus=3, To_bus=6, ), branchIdx: 9, fromBus: 3, toBus: 6, r_pu: 0.009, x_pu: 0.07, b_pu: 0.0, g_pu: 0.0, ratio: 0.0, angle: 0.0, status: 1, tap_ratio: 1.0, phase_shift_deg: 0.0, )
Devices that consume or produce power are addProsumer! calls. The external network injection at B1 references its OWN bus as the voltage reference; that is what makes B1 the slack bus. The generator at B3 feeds in 60 MW, the remaining buses carry loads (p in MW, q in MVar):
addProsumer!(net = net1, busName = "B1", type = "EXTERNALNETWORKINJECTION", referencePri = "B1", vm_pu = 1.02, va_deg = 0.0)
addProsumer!(net = net1, busName = "B3", type = "GENERATOR", p = 60.0, q = 10.0)
addProsumer!(net = net1, busName = "B2", type = "LOAD", p = 35.0, q = 10.0)
addProsumer!(net = net1, busName = "B4", type = "LOAD", p = 45.0, q = 15.0)
addProsumer!(net = net1, busName = "B5", type = "LOAD", p = 25.0, q = 8.0)
addProsumer!(net = net1, busName = "B6", type = "LOAD", p = 30.0, q = 10.0)
addProsumer!(net = net1, busName = "B7", type = "LOAD", p = 20.0, q = 6.0)validate! checks the model for structural problems (unconnected buses, missing slack, inconsistent parameters) BEFORE any numerics run; make it a habit after every round of model edits. Then runpf! runs the rectangular Newton-Raphson solver (max iterations, tolerance, verbosity; status 0 means converged), calcNetLosses! derives branch flows and losses from the converged voltages, and printACPFlowResults prints the classical result tables:
ok1, msg1 = validate!(net = net1)
ok1 || error("Network validation failed: $msg1")
etime, ite = solve!(net1) ## solve! wraps exactly runpf! + calcNetLosses! (see warm-up)
printACPFlowResults(net1, etime, ite, 1e-8)================================================================================
| SPARLECTRA Version 0.9.19 - AC Power Flow Results |
================================================================================
Date : 25-Aug-26 16:19:13
Iterations : 4
Flatstart : No
Tolerance : 1.0e-08
Solver : NR
Total time : 0.000488 s
Case : tour_first_pf
Cooldown iters : 0
Q-hysteresis : 0.0000 pu
Jacobian cond. : kappa1(J) = 45.0, attainable accuracy ~ 1.0e-14, well conditioned (tol 1.0e-8 reachable)
BaseMVA : 100
Nodes : 7 (PV: 0 PQ: 6 Slack: 1)
Grid connection: slack bus B1
Branches : 9
Links : 0
HVDC links : 0
Lines : 9
Trafos : 0
Generators : 2
Loads : 5
Shunts : 0
Controllers : 0 (Tap: 0, Q(U): 0, P(U): 0)
PV→PQ locks : 0
PV→PQ events : 0
total network power balance (Σ S_branch): P = 0.923 [MW], Q = 7.242 [MVar]
==========================================================================================================================================================================================================================
| Nr | Bus | Vn [kV] | V [kV] | V [pu] | phi [deg] | Pg [MW] | Qg [MVar] | Pl [MW] | Ql [MVar] | Ps [MW] | Qs [MVar] | Type | Control | Tap Vm tgt |
==========================================================================================================================================================================================================================
| 1 | B1 | 110.0 | 112.200 | 1.020 | 0.000 | 95.923 | 46.242 | | | | | SLACK | - | |
| 2 | B2 | 110.0 | 109.368 | 0.994 | -2.311 | | | 35.000 | 10.000 | | | PQ | - | |
| 3 | B3 | 110.0 | 109.131 | 0.992 | -1.995 | 60.000 | 10.000 | | | | | PQ | - | |
| 4 | B4 | 110.0 | 107.664 | 0.979 | -3.755 | | | 45.000 | 15.000 | | | PQ | - | |
| 5 | B5 | 110.0 | 108.289 | 0.984 | -3.283 | | | 25.000 | 8.000 | | | PQ | - | |
| 6 | B6 | 110.0 | 108.741 | 0.989 | -2.777 | | | 30.000 | 10.000 | | | PQ | - | |
| 7 | B7 | 110.0 | 110.149 | 1.001 | -1.751 | | | 20.000 | 6.000 | | | PQ | - | |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
==========================================================================================================================================================================================================================
| Branch | Type | Connection | P [MW] | Q [MVar] | P [MW] | Q [MVar] | Pv [MW] | Qv [MVar] | Ctrl | P_tgt | TapPos | Ctrl status |
==========================================================================================================================================================================================================================
| B_ACL_110_1_2 | Line | B1 -> B2 | 54.502 | 27.049 | -54.146 | -24.202 | 0.356 | 2.847 | - | - | - | - |
| B_ACL_110_2_3 | Line | B2 -> B3 | -5.982 | 3.306 | 5.987 | -3.266 | 0.005 | 0.040 | - | - | - | - |
| B_ACL_110_3_4 | Line | B3 -> B4 | 34.553 | 10.606 | -34.394 | -9.412 | 0.159 | 1.195 | - | - | - | - |
| B_ACL_110_4_5 | Line | B4 -> B5 | -10.606 | -5.588 | 10.621 | 5.708 | 0.015 | 0.120 | - | - | - | - |
| B_ACL_110_5_6 | Line | B5 -> B6 | -10.562 | -3.343 | 10.576 | 3.451 | 0.014 | 0.108 | - | - | - | - |
| B_ACL_110_6_7 | Line | B6 -> B7 | -21.151 | -11.066 | 21.221 | 11.590 | 0.070 | 0.525 | - | - | - | - |
| B_ACL_110_7_1 | Line | B7 -> B1 | -41.221 | -17.590 | 41.422 | 19.193 | 0.200 | 1.603 | - | - | - | - |
| B_ACL_110_2_5 | Line | B2 -> B5 | 25.128 | 10.896 | -25.059 | -10.365 | 0.068 | 0.531 | - | - | - | - |
| B_ACL_110_3_6 | Line | B3 -> B6 | 19.460 | 2.659 | -19.425 | -2.385 | 0.035 | 0.274 | - | - | - | - |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Control
-------
Transformer controls: noneReading aid (Example 1.1): the slack at B1 covers the difference between 155 MW of load, 60 MW of scheduled generation, and the network losses; all bus voltages stay near 1.0 pu. Loading a case from a FILE instead is one call through the framework workflow: run_sparlectra(casefile = "case14.m", path = ...) after ensure_casefile("case14.m"), which downloads the case on demand; the result carries the solved net as result.net.
The same construction, packed into a function: later examples reuse this network (model editing in Examples 2.3 and 2.4, state estimation in the advanced tour).
function build_ring7(name::String)
net = Net(name = name, baseMVA = 100.0)
addBus!(net = net, busName = "B1", vn_kV = 110.0, vm_pu = 1.02, va_deg = 0.0)
for i in 2:7
addBus!(net = net, busName = "B$(i)", vn_kV = 110.0, vm_pu = 1.0, va_deg = 0.0)
end
addPIModelACLine!(net = net, fromBus = "B1", toBus = "B2", r_pu = 0.010, x_pu = 0.080, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net, fromBus = "B2", toBus = "B3", r_pu = 0.011, x_pu = 0.085, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net, fromBus = "B3", toBus = "B4", r_pu = 0.012, x_pu = 0.090, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net, fromBus = "B4", toBus = "B5", r_pu = 0.010, x_pu = 0.080, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net, fromBus = "B5", toBus = "B6", r_pu = 0.011, x_pu = 0.085, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net, fromBus = "B6", toBus = "B7", r_pu = 0.012, x_pu = 0.090, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net, fromBus = "B7", toBus = "B1", r_pu = 0.010, x_pu = 0.080, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net, fromBus = "B2", toBus = "B5", r_pu = 0.009, x_pu = 0.070, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net, fromBus = "B3", toBus = "B6", r_pu = 0.009, x_pu = 0.070, b_pu = 0.0, status = 1)
addProsumer!(net = net, busName = "B1", type = "EXTERNALNETWORKINJECTION", referencePri = "B1", vm_pu = 1.02, va_deg = 0.0)
addProsumer!(net = net, busName = "B3", type = "GENERATOR", p = 60.0, q = 10.0)
addProsumer!(net = net, busName = "B2", type = "LOAD", p = 35.0, q = 10.0)
addProsumer!(net = net, busName = "B4", type = "LOAD", p = 45.0, q = 15.0)
addProsumer!(net = net, busName = "B5", type = "LOAD", p = 25.0, q = 8.0)
addProsumer!(net = net, busName = "B6", type = "LOAD", p = 30.0, q = 10.0)
addProsumer!(net = net, busName = "B7", type = "LOAD", p = 20.0, q = 6.0)
ok, msg = validate!(net = net)
ok || error("Network validation failed: $msg")
return net
endbuild_ring7 (generic function with 1 method)Part II: Beginner
Chapter 2: working with the model
Solving once is the easy part. This chapter covers what day-to-day work actually consists of: judging how much the numbers can be trusted, editing and switching the model, exporting it, and letting the solver enforce reactive-power limits.
How much can you trust these numbers?
Every Newton iteration solves the linear system $J \, \Delta x = -F$ with the power-flow Jacobian $J$. The condition number $\kappa(J)$ measures how strongly that solve amplifies tiny perturbations: rounding, measurement noise in the input data, small parameter changes. The attainable relative accuracy in Float64 is roughly $\kappa \cdot 2 \cdot 10^{-16}$, so every power of ten in $\kappa$ costs one significant digit of the result. condestJacobian(net) estimates $\kappa_1$ at the operating point the net currently holds, on the same sparse Jacobian the solver factors. Example 2.1: the condition number of the healthy ring. First the solved 7-bus ring of Example 1.1 (diagram there):
println("ring network: kappa = ", round(condestJacobian(net1), sigdigits = 3))ring network: kappa = 45.0Reading aid (Example 2.1): around 45, excellent. Rule of thumb: below about $10^6$ well conditioned, around $10^{10}$ borderline, beyond $10^{14}$ numerically singular in Float64.
The instructive part is how conditioning degrades when the physics degenerate, long before the solver visibly fails. Example 2.2: a feeder with a degenerating stub. Take a small feeder with a measurement stub at B3 and make the stub line weaker in each round:
(slack)
B1 -------- B2 - - - - B3 stub line B2-B3: x_pu grows
20 MW from 0.08 to 8e10 per roundfor x_weak in (0.08, 800.0, 8.0e6, 8.0e10)
net = Net(name = "tour_cond", baseMVA = 100.0)
addBus!(net = net, busName = "B1", vn_kV = 110.0)
addBus!(net = net, busName = "B2", vn_kV = 110.0)
addBus!(net = net, busName = "B3", vn_kV = 110.0)
addProsumer!(net = net, busName = "B1", type = "EXTERNALNETWORKINJECTION", referencePri = "B1", vm_pu = 1.0, va_deg = 0.0)
addProsumer!(net = net, busName = "B2", type = "ENERGYCONSUMER", p = 20.0, q = 5.0)
addPIModelACLine!(net = net, fromBus = "B1", toBus = "B2", r_pu = 0.01, x_pu = 0.08, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net, fromBus = "B2", toBus = "B3", r_pu = x_weak / 8, x_pu = x_weak, b_pu = 0.0, status = 1)
_, ite_weak = solve!(net)
vm3 = round(something(net.nodeVec[3]._vm_pu, NaN); digits = 4)
println("x = ", lpad(x_weak, 8), " pu: ", ite_weak, " iterations, Vm(B3) = ", vm3, ", kappa = ", round(condestJacobian(net), sigdigits = 3))
endx = 0.08 pu: 4 iterations, Vm(B3) = 0.9938, kappa = 11.6
x = 800.0 pu: 4 iterations, Vm(B3) = 0.9938, kappa = 13000.0
x = 8.0e6 pu: 4 iterations, Vm(B3) = 0.9938, kappa = 1.3e8
x = 8.0e10 pu: 4 iterations, Vm(B3) = 0.9938, kappa = 1.3e12Reading aid (Example 2.2): the solver converges in 4 iterations in every round, and Vm(B3) prints the same plausible 0.9938 each time. Nothing in the result table reveals that the last round sits at $\kappa \approx 10^{12}$, where only about 4 significant digits survive: the printed voltage is already at the edge of what the arithmetic can guarantee, and any sensitivity built on this Jacobian (voltage per tap step, voltage per MVar) is numerically meaningless. That is exactly what the estimate is for: the classic result log reports it as a Jacobian cond. line, and diagnose runs grade it with a plain-language verdict.
Editing, switching, exporting
Example 2.3: an edit round on the ring. Model work is iterative: change a parameter, switch an element, remove one, validate, solve again; the stage is a fresh copy of the 7-bus ring of Example 1.1 (diagram there). The dedicated helpers keep the bookkeeping consistent (branch indices, prosumer injections, isolated buses):
net_edit = build_ring7("tour_edit")
# stiffen the B1-B2 line (per-branch parameter update)
brVec = getNetBranchNumberVec(net = net_edit, fromBus = "B1", toBus = "B2")
updateBranchParameters!(net = net_edit, branchNr = brVec[1], branch = BranchModel(0.005, 0.040, 0.0, 0.0, 0.0, 0.0, 100.0))
# add 5 MW / 1 MVAr of load at B4. Loads and generators are PROSUMER
# objects and the AC solver reads its injections from them, so growing a
# load means adding (or editing) a prosumer. The node-sum helpers
# (addBusLoadPower!) only feed the report layer, not the AC solve (#323).
addProsumer!(net = net_edit, busName = "B4", type = "LOAD", p = 5.0, q = 1.0)
# switch the B3-B6 cross-tie out of service (aggregate switch; a single
# terminal would be setBranchTerminalStatus!, see Example 2.6)
tie = getNetBranchNumberVec(net = net_edit, fromBus = "B3", toBus = "B6")
setNetBranchStatus!(net = net_edit, branchNr = tie[1], status = 0)
# remove the B2-B5 cross-tie outright, then re-validate and solve
removeACLine!(net = net_edit, fromBus = "B2", toBus = "B5")
markIsolatedBuses!(net = net_edit, log = false)
ok_e, msg_e = validate!(net = net_edit)
ok_e || error("edit round left the net invalid: $msg_e")
_, ite_e = solve!(net_edit)
println("edited net solves in ", ite_e, " iterations; branches now: ", length(net_edit.branchVec))
# the edited model exports as a MATPOWER case file
case_out = joinpath(mktempdir(), "tour_edit.m")
writeMatpowerCasefile(net_edit, case_out)
println("exported: ", case_out, " (", filesize(case_out), " bytes)")edited net solves in 5 iterations; branches now: 8
[ Info: convertion to Matpower CASE-Files, Testcase: (tour_edit)
exported: /tmp/jl_eLniP9/tour_edit.m (3345 bytes)Reading aid (Example 2.3): removal helpers (removeACLine!, removeTrafo!, removeProsumer!, ...) mutate the net and can leave isolated buses behind; markIsolatedBuses! flags them for the solver and clearIsolatedBuses! deletes the safe ones. removeBus! deliberately only CHECKS removability. Re-validate after every edit round.
Example 2.4: the node-level power trap. One trap worth demonstrating (issue #323), still on the edited ring of Example 2.3: there are also NODE-level power helpers (addBusLoadPower!, addBusGenPower!). They edit report sums that NO solver reads; the solvers build their injections from the prosumer objects. So this "edit" changes nothing:
vm_before = get_bus_vm_pu(net_edit, "B4")
addBusLoadPower!(net = net_edit, busName = "B4", p = 25.0, q = 5.0) ## report layer only!
solve!(net_edit)
println("after addBusLoadPower!(+25 MW): Vm(B4) = ", round(get_bus_vm_pu(net_edit, "B4"); digits = 4), " pu (before: ", round(vm_before; digits = 4), " pu, unchanged)")
addProsumer!(net = net_edit, busName = "B4", type = "LOAD", p = 25.0, q = 5.0) ## THIS is a load
solve!(net_edit)
println("after addProsumer!(LOAD, 25 MW): Vm(B4) = ", round(get_bus_vm_pu(net_edit, "B4"); digits = 4), " pu (sags, the solver saw it)")after addBusLoadPower!(+25 MW): Vm(B4) = 0.9706 pu (before: 0.9706 pu, unchanged)
after addProsumer!(LOAD, 25 MW): Vm(B4) = 0.9556 pu (sags, the solver saw it)Reactive-power limits (PV to PQ switching)
A voltage-regulating machine holds its bus voltage only while its reactive power stays inside [qMin, qMax]. When the solver hits a limit, it switches the bus from PV to PQ at the violated bound within the Newton iteration (the active-set strategy) and reports every event. Example 2.5: a machine pinned at its Q-limit. A three-bus chain, the regulating machine in the middle:
(slack)
Q1 -------- Q2 -------- Q3
gen 20 MW load 45 MW / 20 MVAr
1.05 pu, Q in
[-5, 5] MVArnet_q = Net(name = "tour_qlimits", baseMVA = 100.0)
for b in ("Q1", "Q2", "Q3")
addBus!(net = net_q, busName = b, vn_kV = 110.0)
end
addProsumer!(net = net_q, busName = "Q1", type = "EXTERNALNETWORKINJECTION", referencePri = "Q1", vm_pu = 1.0, va_deg = 0.0)
addProsumer!(net = net_q, busName = "Q2", type = "GENERATOR", p = 20.0, vm_pu = 1.05, qMin = -5.0, qMax = 5.0)
addProsumer!(net = net_q, busName = "Q3", type = "ENERGYCONSUMER", p = 45.0, q = 20.0)
addPIModelACLine!(net = net_q, fromBus = "Q1", toBus = "Q2", r_pu = 0.01, x_pu = 0.08, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net_q, fromBus = "Q2", toBus = "Q3", r_pu = 0.01, x_pu = 0.08, b_pu = 0.0, status = 1)
validate!(net = net_q)
solve!(net_q)
println("Vm(Q2) = ", round(get_bus_vm_pu(net_q, "Q2"); digits = 4), " pu (setpoint was 1.05)")
printQLimitLog(net_q)
distributeBusResults!(net_q)Vm(Q2) = 0.9833 pu (setpoint was 1.05)
PV->PQ switching events:
total events : 1
rows shown : 1
rows omitted : 0
──────────────────────────────────
Iteration │ Bus │ Side
──────────────────────────────────
2 │ 2 │ max
──────────────────────────────────Reading aid (Example 2.5): holding 1.05 pu at Q2 would need more than the 5 MVar the machine may deliver, so the solver pins Q at qMax and lets the voltage float below the setpoint; the log names bus, iteration, and bound. distributeBusResults! pushes the solved bus totals back onto the individual prosumers; with several machines on one bus it redistributes water-filling style, so no unit leaves its Q range. The deep dive (enforcement modes, guards, oscillation handling) is on the Q-limit strategy page.
Opening one end of a line
A breaker can open a single terminal while the other stays connected. The line then carries no through flow, but it does NOT disappear: seen from the closed bus it collapses to its exact pi reduction and keeps drawing its FULL charging (for realistic lines the two shunt arms act almost in parallel, so it is g + jb, not half of it), plus the small ohmic loss of the charging current. The voltage at the open end rises above the feeding bus, the classical Ferranti effect, and is reported as a branch result without adding a bus. Example 2.6: the Ferranti rise, and why "fully disconnected" is the wrong model for it. Three states of the same network: line closed, the breaker at the B end open, and the line treated as completely disconnected. The second corridor to C keeps the system solvable in every state, so the states are comparable:
(slack)
C ------- A =============== B A=B: one long 380-kV line, b_pu = 0.9;
50 MW 120 MW the breaker at the B end opens# Example 2.6, state 1 (closed): the long line feeds the 120-MW load
net_open = Net(name = "tour_open_end", baseMVA = 100.0)
for b in ("A", "B", "C")
addBus!(net = net_open, busName = b, vn_kV = 380.0)
end
addProsumer!(net = net_open, busName = "A", type = "EXTERNALNETWORKINJECTION", referencePri = "A", vm_pu = 1.0, va_deg = 0.0)
addProsumer!(net = net_open, busName = "B", type = "ENERGYCONSUMER", p = 120.0, q = 30.0)
addProsumer!(net = net_open, busName = "C", type = "ENERGYCONSUMER", p = 50.0, q = 10.0)
addPIModelACLine!(net = net_open, fromBus = "A", toBus = "B", r_pu = 0.02, x_pu = 0.16, b_pu = 0.9, g_pu = 0.004, status = 1)
addPIModelACLine!(net = net_open, fromBus = "A", toBus = "C", r_pu = 0.01, x_pu = 0.08, b_pu = 0.02, status = 1)
validate!(net = net_open)
solve!(net_open)
println("state 1, closed : line A=B carries ", round(get_branch_p_from_to_mw(net_open, "A", "B"); digits = 1), " MW to the load")
# Example 2.6, state 2 (open@to): open the breaker at the B end and
# re-solve; the classical result tables below show the consequences
setBranchTerminalStatus!(net_open.branchVec[1]; to = false)
markIsolatedBuses!(net = net_open, log = false)
etime_open, ite_open = solve!(net_open)
br_open = net_open.branchVec[1]
q_slack_open = get_branch_q_from_to_mvar(net_open, "A", "B") + get_branch_q_from_to_mvar(net_open, "A", "C")
println("state 2, open@to: charging draw ", round(br_open.fBranchFlow.qFlow; digits = 1), " MVAr, active loss ", round(br_open.fBranchFlow.pFlow; digits = 3), " MW")
println(" voltage at the OPEN end: ", round(br_open.open_end_vm_pu; digits = 4), " pu, HIGHER than the ", round(get_bus_vm_pu(net_open, "A"); digits = 2), " pu at the feeding bus A")
println(" (Ferranti effect: the charging current flowing through the line reactance lifts the voltage toward the open end)")
# the classical result print of state 2: the branch row carries the
# open@to marker, the header counts one open terminal, and the isolated
# bus B shows the OPEN-END voltage in its V columns (the Ferranti value,
# flagged open-end in the Control column)
printACPFlowResults(net_open, etime_open, ite_open, 1e-8)
# Example 2.6, state 3 (fully disconnected): the WRONG model for an open
# breaker end; the pi stub vanishes from the Y-bus and with it the
# charging draw and the Ferranti information
setBranchTerminalStatus!(net_open.branchVec[1]; from = false)
markIsolatedBuses!(net = net_open, log = false)
solve!(net_open)
q_slack_off = get_branch_q_from_to_mvar(net_open, "A", "C")
println("state 3, fully disconnected: open-end voltage ", br_open.open_end_vm_pu === nothing ? "gone" : "?", ", slack reactive supply now ", round(q_slack_off; digits = 1), " MVAr")
println("reactive balance shift state 2 -> state 3: ", round(q_slack_off - q_slack_open; digits = 1), " MVAr of charging draw silently vanished")state 1, closed : line A=B carries 123.5 MW to the load
state 2, open@to: charging draw -93.5 MVAr, active loss 0.902 MW
voltage at the OPEN end: 1.0775 pu, HIGHER than the 1.0 pu at the feeding bus A
(Ferranti effect: the charging current flowing through the line reactance lifts the voltage toward the open end)
================================================================================
| SPARLECTRA Version 0.9.19 - AC Power Flow Results |
================================================================================
Date : 25-Aug-26 16:19:16
Iterations : 1
Flatstart : No
Tolerance : 1.0e-08
Solver : NR
Total time : 0.000161 s
Case : tour_open_end
Cooldown iters : 0
Q-hysteresis : 0.0000 pu
Jacobian cond. : kappa1(J) = 1.42, attainable accuracy ~ 3.2e-16, well conditioned (tol 1.0e-8 reachable)
BaseMVA : 100
Nodes : 3 (PV: 0 PQ: 2 Slack: 1)
Grid connection: slack bus A
Branches : 2
Open terminals : 1 (branches open at one terminal)
open end : B_ACL_380_1_2 at B: Vm 1.0775 pu, Va -0.575 deg (+7.75 % vs feeding bus A at 1.0000 pu)
Links : 0
HVDC links : 0
Lines : 2
Trafos : 0
Generators : 1
Loads : 2
Shunts : 0
Controllers : 0 (Tap: 0, Q(U): 0, P(U): 0)
PV→PQ locks : 0
PV→PQ events : 0
total network power balance (Σ S_branch): P = 1.167 [MW], Q = -93.335 [MVar]
==========================================================================================================================================================================================================================
| Nr | Bus | Vn [kV] | V [kV] | V [pu] | phi [deg] | Pg [MW] | Qg [MVar] | Pl [MW] | Ql [MVar] | Ps [MW] | Qs [MVar] | Type | Control | Tap Vm tgt |
==========================================================================================================================================================================================================================
| 1 | A | 380.0 | 380.000 | 1.000 | 0.000 | 51.167 | -83.335 | | | | | SLACK | - | |
| 2 | B | 380.0 | 409.444 | 1.077 | -0.575 | | | 120.000 | 30.000 | | | PQ | open-end | |
| 3 | C | 380.0 | 374.996 | 0.987 | -2.271 | | | 50.000 | 10.000 | | | PQ | - | |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
==========================================================================================================================================================================================================================
| Branch | Type | Connection | P [MW] | Q [MVar] | P [MW] | Q [MVar] | Pv [MW] | Qv [MVar] | Ctrl | P_tgt | TapPos | Ctrl status |
==========================================================================================================================================================================================================================
| B_ACL_380_1_2 | Line | A -> B | 0.902 | -93.482 | 0.000 | 0.000 | 0.902 | -93.482 | - | - | - | open@to 1.0775pu |
| B_ACL_380_1_3 | Line | A -> C | 50.265 | 10.147 | -50.000 | -10.000 | 0.265 | 0.147 | - | - | - | - |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Control
-------
Transformer controls: none
state 3, fully disconnected: open-end voltage gone, slack reactive supply now 10.1 MVAr
reactive balance shift state 2 -> state 3: 93.5 MVAr of charging draw silently vanishedReading aid (Example 2.6): in state 2 the classical tables carry the whole story: the branch row is marked open@to with the open-end voltage, the header counts it under Open terminals, and the isolated bus B shows the Ferranti voltage (1.0775 pu) in its V columns, flagged open-end because it is a branch RESULT (open_end_vm_pu) measured at the open breaker, not a solved bus voltage. State 3 is the modeling trap the feature exists for: treating the one-sided opening as a full disconnect hides roughly 93 MVAr of charging draw from the reactive balance and erases the Ferranti overvoltage a protection engineer would care about. The full story, including the Schur reduction and why it is the full charging, is on the branch-model docs page under "One-sided open branches"; the runnable twin is exp_open_terminal_line.jl.
Links: connections without impedance
A link (addLink!) models a busbar coupler or sectionalizer: a closed switch between two buses. It is NOT a branch. It has no impedance, it is never stamped into the Y-bus, and it never appears in the branch table. Instead the solver contracts every cluster of buses joined by closed links onto one representative bus before the Y-bus is built, so all linked buses share one voltage by construction. (Do not confuse these links with the HVDC "Link" rows of the advanced tour's chapter 2: a bus link is a switch, an HVDC link is a converter pair.)
Because the link has no admittance, the power flow cannot tell how much power crosses it: that is reconstructed AFTER the solve, from Kirchhoff's current law, with calcLinkFlowsKCL!. Example 2.7: a zero-impedance ring of links. The interesting case is a ring of links, a zero-impedance cycle:
S (slack)
|
| real line (r = 0.01, x = 0.08)
|
R1
/ \ R1, R2, R3 joined by three closed links:
/ \ an impedance-less ring, electrically ONE node
R3 --- R2
(20 MW) (30 MW)In a zero-impedance loop the flow split is physically NOT unique: any circulating current can be added without changing a single voltage. Sparlectra returns the minimum-norm KCL solution (Moore-Penrose pseudoinverse), the unique split with zero artificial circulation, so the result is deterministic and reproducible:
net_ring = Net(name = "tour_link_ring", baseMVA = 100.0)
addBus!(net = net_ring, busName = "S", vn_kV = 110.0)
for b in ("R1", "R2", "R3")
addBus!(net = net_ring, busName = b, vn_kV = 110.0)
end
addProsumer!(net = net_ring, busName = "S", type = "EXTERNALNETWORKINJECTION", referencePri = "S", vm_pu = 1.0, va_deg = 0.0)
addProsumer!(net = net_ring, busName = "R2", type = "ENERGYCONSUMER", p = 30.0, q = 8.0)
addProsumer!(net = net_ring, busName = "R3", type = "ENERGYCONSUMER", p = 20.0, q = 5.0)
addPIModelACLine!(net = net_ring, fromBus = "S", toBus = "R1", r_pu = 0.01, x_pu = 0.08, b_pu = 0.0, status = 1)
addLink!(net = net_ring, fromBus = "R1", toBus = "R2", status = 1)
addLink!(net = net_ring, fromBus = "R2", toBus = "R3", status = 1)
addLink!(net = net_ring, fromBus = "R3", toBus = "R1", status = 1)
validate!(net = net_ring)
solve!(net_ring)
calcLinkFlowsKCL!(net_ring)
println("one voltage for the whole ring: Vm(R1/R2/R3) = ", join((round(get_bus_vm_pu(net_ring, b); digits = 5) for b in ("R1", "R2", "R3")), " / "), " pu")
ring_bus_name = Dict(v => k for (k, v) in net_ring.busDict) ## BusLink stores bus INDICES
for l in net_ring.linkVec
println("link ", ring_bus_name[l.fromBus], " -> ", ring_bus_name[l.toBus], ": ", lpad(round(l.pFlow_MW; digits = 2), 7), " MW")
endone voltage for the whole ring: Vm(R1/R2/R3) = 0.98357 / 0.98357 / 0.98357 pu
link R1 -> R2: 26.67 MW
link R2 -> R3: -3.33 MW
link R3 -> R1: -23.33 MWReading aid (Example 2.7): 50 MW enter the ring at R1. The minimum-norm split sends 26.67 MW directly to the 30 MW load at R2 and 23.33 MW the other way round to R3; the third coupler carries only the 3.33 MW that R2 still needs. A negative sign just means the flow runs against the link's from-to direction. Any other split (say 30 and 20 with an idle third coupler) would satisfy KCL too, but only by adding a circulating component; the pseudoinverse is exactly the split without one. The links page of the docs has the math and the modeling guidelines (for example: never link the slack bus itself).
Part III: Advanced
Chapter 3: slack types and short-circuit currents
One grid connection, modeled three ways, plus an IEC 60909-0 fault-current sweep from the declared feeder data. The detailed walk-through with full reading aids is the slack-types notebook.
All examples of this chapter share one 8-bus network (build_grid): a ring with two chords, generators at B3 and B6, loads at B2, B4, B7, and B8, and at B1 the grid connection whose model the examples vary:
(grid) G
B1 ------ B2 ------ B3 ------ B4
| | | | ring of eight buses plus
| | | | the chords B2-B7 and B3-B6
B8 ------ B7 ------ B6 ------ B5
Gfunction build_grid(mode::Symbol)
net = Net(name = "tour_eg8_$(mode)", baseMVA = 100.0)
for b in ("B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8")
addBus!(net = net, busName = b, vn_kV = 110.0)
end
addPIModelACLine!(net = net, fromBus = "B1", toBus = "B2", r_pu = 0.010, x_pu = 0.060, b_pu = 0.02, status = 1)
addPIModelACLine!(net = net, fromBus = "B2", toBus = "B3", r_pu = 0.015, x_pu = 0.080, b_pu = 0.02, status = 1)
addPIModelACLine!(net = net, fromBus = "B3", toBus = "B4", r_pu = 0.020, x_pu = 0.090, b_pu = 0.02, status = 1)
addPIModelACLine!(net = net, fromBus = "B4", toBus = "B5", r_pu = 0.012, x_pu = 0.070, b_pu = 0.02, status = 1)
addPIModelACLine!(net = net, fromBus = "B5", toBus = "B6", r_pu = 0.015, x_pu = 0.075, b_pu = 0.02, status = 1)
addPIModelACLine!(net = net, fromBus = "B6", toBus = "B7", r_pu = 0.018, x_pu = 0.085, b_pu = 0.02, status = 1)
addPIModelACLine!(net = net, fromBus = "B7", toBus = "B8", r_pu = 0.010, x_pu = 0.055, b_pu = 0.02, status = 1)
addPIModelACLine!(net = net, fromBus = "B8", toBus = "B1", r_pu = 0.011, x_pu = 0.065, b_pu = 0.02, status = 1)
addPIModelACLine!(net = net, fromBus = "B2", toBus = "B7", r_pu = 0.020, x_pu = 0.100, b_pu = 0.02, status = 1)
addPIModelACLine!(net = net, fromBus = "B3", toBus = "B6", r_pu = 0.022, x_pu = 0.110, b_pu = 0.02, status = 1)
addProsumer!(net = net, busName = "B3", type = "GENERATOR", p = 60.0, vm_pu = 1.01, qMin = -60.0, qMax = 60.0)
addProsumer!(net = net, busName = "B6", type = "GENERATOR", p = 40.0, vm_pu = 1.00, qMin = -40.0, qMax = 40.0)
addProsumer!(net = net, busName = "B2", type = "ENERGYCONSUMER", p = 45.0, q = 12.0)
addProsumer!(net = net, busName = "B4", type = "ENERGYCONSUMER", p = 50.0, q = 15.0)
addProsumer!(net = net, busName = "B7", type = "ENERGYCONSUMER", p = 40.0, q = 10.0)
addProsumer!(net = net, busName = "B8", type = "ENERGYCONSUMER", p = 25.0, q = 8.0)
addExternalGrid!(net = net, busName = "B1", vm_pu = 1.02, sk_max_MVA = 2000.0, sk_min_MVA = 1500.0, rx_max = 0.1, internal_impedance = (mode === :source))
ok, msg = validate!(net = net)
ok || error("Network validation failed: $msg")
return net
endbuild_grid (generic function with 1 method)Example 3.1: the ideal slack. B1 is pinned at exactly 1.02 pu / 0° and absorbs the whole imbalance (the SLACK row).
net_slack = build_grid(:slack)
etime, ite = solve!(net_slack)
printACPFlowResults(net_slack, etime, ite, 1e-8)================================================================================
| SPARLECTRA Version 0.9.19 - AC Power Flow Results |
================================================================================
Date : 25-Aug-26 16:19:17
Iterations : 4
Flatstart : No
Tolerance : 1.0e-08
Solver : NR
Total time : 0.000486 s
Case : tour_eg8_slack
Cooldown iters : 0
Q-hysteresis : 0.0000 pu
Jacobian cond. : kappa1(J) = 310.0, attainable accuracy ~ 6.9e-14, well conditioned (tol 1.0e-8 reachable)
BaseMVA : 100
Nodes : 8 (PV: 2 PQ: 5 Slack: 1)
Grid connection: slack bus B1
Branches : 10
Links : 0
HVDC links : 0
Lines : 10
Trafos : 0
Generators : 3
Loads : 4
Shunts : 0
Controllers : 0 (Tap: 0, Q(U): 0, P(U): 0)
PV→PQ locks : 0
PV→PQ events : 0
total network power balance (Σ S_branch): P = 0.821 [MW], Q = -15.938 [MVar]
==========================================================================================================================================================================================================================
| Nr | Bus | Vn [kV] | V [kV] | V [pu] | phi [deg] | Pg [MW] | Qg [MVar] | Pl [MW] | Ql [MVar] | Ps [MW] | Qs [MVar] | Type | Control | Tap Vm tgt |
==========================================================================================================================================================================================================================
| 1 | B1 | 110.0 | 112.200 | 1.020 | 0.000 | 60.821 | 30.126 | | | | | SLACK | - | |
| 2 | B2 | 110.0 | 110.839 | 1.008 | -0.871 | | | 45.000 | 12.000 | | | PQ | - | |
| 3 | B3 | 110.0 | 111.100 | 1.010 | 0.208 | 60.000 | 16.114 | | | | | PV | - | |
| 4 | B4 | 110.0 | 109.258 | 0.993 | -1.342 | | | 50.000 | 15.000 | | | PQ | - | |
| 5 | B5 | 110.0 | 109.667 | 0.997 | -0.651 | | | | | | | PQ | - | |
| 6 | B6 | 110.0 | 110.000 | 1.000 | 0.098 | 40.000 | -17.177 | | | | | PV | - | |
| 7 | B7 | 110.0 | 110.117 | 1.001 | -1.229 | | | 40.000 | 10.000 | | | PQ | - | |
| 8 | B8 | 110.0 | 110.733 | 1.007 | -1.065 | | | 25.000 | 8.000 | | | PQ | - | |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
==========================================================================================================================================================================================================================
| Branch | Type | Connection | P [MW] | Q [MVar] | P [MW] | Q [MVar] | Pv [MW] | Qv [MVar] | Ctrl | P_tgt | TapPos | Ctrl status |
==========================================================================================================================================================================================================================
| B_ACL_110_1_2 | Line | B1 -> B2 | 28.790 | 15.390 | -28.685 | -16.812 | 0.106 | -1.422 | - | - | - | - |
| B_ACL_110_2_3 | Line | B2 -> B3 | -23.652 | 0.659 | 23.735 | -2.252 | 0.083 | -1.592 | - | - | - | - |
| B_ACL_110_3_4 | Line | B3 -> B4 | 32.805 | 10.894 | -32.566 | -11.826 | 0.239 | -0.932 | - | - | - | - |
| B_ACL_110_4_5 | Line | B4 -> B5 | -17.434 | -3.174 | 17.471 | 1.412 | 0.038 | -1.761 | - | - | - | - |
| B_ACL_110_5_6 | Line | B5 -> B6 | -17.471 | -1.412 | 17.517 | -0.351 | 0.046 | -1.763 | - | - | - | - |
| B_ACL_110_6_7 | Line | B6 -> B7 | 25.925 | -7.426 | -25.796 | 6.030 | 0.128 | -1.396 | - | - | - | - |
| B_ACL_110_7_8 | Line | B7 -> B8 | -6.883 | -9.939 | 6.896 | 7.993 | 0.013 | -1.946 | - | - | - | - |
| B_ACL_110_8_1 | Line | B8 -> B1 | -31.896 | -15.993 | 32.031 | 14.736 | 0.135 | -1.257 | - | - | - | - |
| B_ACL_110_2_7 | Line | B2 -> B7 | 7.336 | 4.152 | -7.320 | -6.091 | 0.016 | -1.938 | - | - | - | - |
| B_ACL_110_3_6 | Line | B3 -> B6 | 3.460 | 7.471 | -3.442 | -9.401 | 0.018 | -1.929 | - | - | - | - |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Control
-------
Transformer controls: noneExample 3.2: the non-ideal source. With internal_impedance = true the setpoint moves to the hidden internal bus (last row, type SOURCE); the terminal B1 in the first row droops below 1.02 pu.
net_source = build_grid(:source)
etime, ite = solve!(net_source)
printACPFlowResults(net_source, etime, ite, 1e-8)================================================================================
| SPARLECTRA Version 0.9.19 - AC Power Flow Results |
================================================================================
Date : 25-Aug-26 16:19:17
Iterations : 4
Flatstart : No
Tolerance : 1.0e-08
Solver : NR
Total time : 0.000346 s
Case :tour_eg8_source
Cooldown iters : 0
Q-hysteresis : 0.0000 pu
Jacobian cond. : kappa1(J) = 378.0, attainable accuracy ~ 8.4e-14, well conditioned (tol 1.0e-8 reachable)
BaseMVA : 100
Nodes : 9 (PV: 2 PQ: 6 Slack: 0 Source: 1)
Grid connection: external-grid source at B1 (Sk'' = 2000.0 MVA, R/X = 0.1; internal slack: B1__extgrid_int)
Branches : 11
Links : 0
HVDC links : 0
Lines : 11
Trafos : 0
Generators : 3
Loads : 4
Shunts : 0
Controllers : 0 (Tap: 0, Q(U): 0, P(U): 0)
PV→PQ locks : 0
PV→PQ events : 0
total network power balance (Σ S_branch): P = 0.974 [MW], Q = -14.086 [MVar]
==========================================================================================================================================================================================================================
| Nr | Bus | Vn [kV] | V [kV] | V [pu] | phi [deg] | Pg [MW] | Qg [MVar] | Pl [MW] | Ql [MVar] | Ps [MW] | Qs [MVar] | Type | Control | Tap Vm tgt |
==========================================================================================================================================================================================================================
| 1 | B1 | 110.0 | 110.952 | 1.009 | -1.640 | | | | | | | PQ | - | |
| 2 | B2 | 110.0 | 110.156 | 1.001 | -2.578 | | | 45.000 | 12.000 | | | PQ | - | |
| 3 | B3 | 110.0 | 111.100 | 1.010 | -1.562 | 60.000 | 23.923 | | | | | PV | - | |
| 4 | B4 | 110.0 | 109.258 | 0.993 | -3.114 | | | 50.000 | 15.000 | | | PQ | - | |
| 5 | B5 | 110.0 | 109.667 | 0.997 | -2.424 | | | | | | | PQ | - | |
| 6 | B6 | 110.0 | 110.000 | 1.000 | -1.675 | 40.000 | -11.017 | | | | | PV | - | |
| 7 | B7 | 110.0 | 109.538 | 0.996 | -2.950 | | | 40.000 | 10.000 | | | PQ | - | |
| 8 | B8 | 110.0 | 109.843 | 0.999 | -2.755 | | | 25.000 | 8.000 | | | PQ | - | |
| 9 | B1__extgrid_int | 110.0 | 112.200 | 1.020 | 0.000 | 60.974 | 18.008 | | | | | SOURCE | - | |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
==========================================================================================================================================================================================================================
| Branch | Type | Connection | P [MW] | Q [MVar] | P [MW] | Q [MVar] | Pv [MW] | Qv [MVar] | Ctrl | P_tgt | TapPos | Ctrl status |
==========================================================================================================================================================================================================================
| B_ACL_110_1_2 | Line | B1 -> B2 | 28.829 | 6.563 | -28.742 | -8.060 | 0.087 | -1.496 | - | - | - | - |
| B_ACL_110_2_3 | Line | B2 -> B3 | -23.568 | -7.122 | 23.657 | 5.572 | 0.089 | -1.550 | - | - | - | - |
| B_ACL_110_3_4 | Line | B3 -> B4 | 32.830 | 10.890 | -32.591 | -11.821 | 0.239 | -0.931 | - | - | - | - |
| B_ACL_110_4_5 | Line | B4 -> B5 | -17.409 | -3.179 | 17.447 | 1.417 | 0.037 | -1.762 | - | - | - | - |
| B_ACL_110_5_6 | Line | B5 -> B6 | -17.447 | -1.417 | 17.493 | -0.347 | 0.046 | -1.764 | - | - | - | - |
| B_ACL_110_6_7 | Line | B6 -> B7 | 26.002 | -1.280 | -25.880 | -0.137 | 0.122 | -1.417 | - | - | - | - |
| B_ACL_110_7_8 | Line | B7 -> B8 | -6.823 | -4.758 | 6.829 | 2.803 | 0.006 | -1.955 | - | - | - | - |
| B_ACL_110_8_1 | Line | B8 -> B1 | -31.829 | -10.803 | 31.952 | 9.511 | 0.122 | -1.291 | - | - | - | - |
| B_ACL_110_2_7 | Line | B2 -> B7 | 7.310 | 3.182 | -7.296 | -5.106 | 0.014 | -1.924 | - | - | - | - |
| B_ACL_110_3_6 | Line | B3 -> B6 | 3.513 | 7.461 | -3.495 | -9.390 | 0.018 | -1.929 | - | - | - | - |
| B_ACL_110_9_1 | Line | B1__extgrid_int -> B1 | 60.974 | 18.008 | -60.781 | -16.075 | 0.193 | 1.933 | - | - | - | - |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Control
-------
Transformer controls: noneExample 3.3: distributed slack. The generators pick up the imbalance according to their scheduled output (0.6/0.4); the slack row keeps only the reactive balance.
net_dist = build_grid(:slack)
etime, ite = solve!(net_dist; distributed_slack_enabled = true, distributed_slack_p_mode = :pg_weighted)
printACPFlowResults(net_dist, etime, ite, 1e-8)================================================================================
| SPARLECTRA Version 0.9.19 - AC Power Flow Results |
================================================================================
Date : 25-Aug-26 16:19:17
Iterations : 4
Flatstart : No
Tolerance : 1.0e-08
Solver : NR
Total time : 0.000442 s
Case : tour_eg8_slack
Cooldown iters : 0
Q-hysteresis : 0.0000 pu
Jacobian cond. : kappa1(J) = 306.0, attainable accuracy ~ 6.8e-14, well conditioned (tol 1.0e-8 reachable)
BaseMVA : 100
Nodes : 8 (PV: 2 PQ: 5 Slack: 1)
Grid connection: slack bus B1
Branches : 10
Links : 0
HVDC links : 0
Lines : 10
Trafos : 0
Generators : 3
Loads : 4
Shunts : 0
Controllers : 0 (Tap: 0, Q(U): 0, P(U): 0)
PV→PQ locks : 0
PV→PQ events : 0
Dist. slack : mode pg_weighted, lambda_P = +61.537 MW (imbalance + losses picked up by 2 participant(s), see the dSl alpha column)
total network power balance (Σ S_branch): P = 1.537 [MW], Q = -12.415 [MVar]
====================================================================================================================================================================================================================================================
| Nr | Bus | Vn [kV] | V [kV] | V [pu] | phi [deg] | Pg [MW] | Qg [MVar] | Pl [MW] | Ql [MVar] | Ps [MW] | Qs [MVar] | Type | Control | Tap Vm tgt | dSl alpha | Pg eff MW |
====================================================================================================================================================================================================================================================
| 1 | B1 | 110.0 | 112.200 | 1.020 | 0.000 | | 41.841 | | | | | SLACK | - | | | |
| 2 | B2 | 110.0 | 110.763 | 1.007 | 0.460 | | | 45.000 | 12.000 | | | PQ | - | | | |
| 3 | B3 | 110.0 | 111.100 | 1.010 | 3.164 | 60.000 | 11.252 | | | | | PV | - | | 0.6000 | 96.922 |
| 4 | B4 | 110.0 | 109.256 | 0.993 | 1.584 | | | 50.000 | 15.000 | | | PQ | - | | | |
| 5 | B5 | 110.0 | 109.667 | 0.997 | 2.252 | | | | | | | PQ | - | | | |
| 6 | B6 | 110.0 | 110.000 | 1.000 | 2.977 | 40.000 | -20.508 | | | | | PV | - | | 0.4000 | 64.615 |
| 7 | B7 | 110.0 | 110.026 | 1.000 | 0.326 | | | 40.000 | 10.000 | | | PQ | - | | | |
| 8 | B8 | 110.0 | 110.681 | 1.006 | -0.231 | | | 25.000 | 8.000 | | | PQ | - | | | |
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
==========================================================================================================================================================================================================================
| Branch | Type | Connection | P [MW] | Q [MVar] | P [MW] | Q [MVar] | Pv [MW] | Qv [MVar] | Ctrl | P_tgt | TapPos | Ctrl status |
==========================================================================================================================================================================================================================
| B_ACL_110_1_2 | Line | B1 -> B2 | -9.761 | 22.847 | 9.825 | -24.517 | 0.064 | -1.670 | - | - | - | - |
| B_ACL_110_2_3 | Line | B2 -> B3 | -58.389 | 7.496 | 58.904 | -6.783 | 0.515 | 0.713 | - | - | - | - |
| B_ACL_110_3_4 | Line | B3 -> B4 | 33.362 | 10.800 | -33.116 | -11.701 | 0.246 | -0.901 | - | - | - | - |
| B_ACL_110_4_5 | Line | B4 -> B5 | -16.884 | -3.299 | 16.919 | 1.524 | 0.035 | -1.774 | - | - | - | - |
| B_ACL_110_5_6 | Line | B5 -> B6 | -16.919 | -1.524 | 16.963 | -0.254 | 0.043 | -1.778 | - | - | - | - |
| B_ACL_110_6_7 | Line | B6 -> B7 | 52.290 | -11.096 | -51.779 | 11.506 | 0.511 | 0.410 | - | - | - | - |
| B_ACL_110_7_8 | Line | B7 -> B8 | 15.333 | -14.520 | -15.291 | 12.737 | 0.042 | -1.783 | - | - | - | - |
| B_ACL_110_8_1 | Line | B8 -> B1 | -9.709 | -20.737 | 9.761 | 18.994 | 0.053 | -1.743 | - | - | - | - |
| B_ACL_110_2_7 | Line | B2 -> B7 | 3.564 | 5.021 | -3.554 | -6.987 | 0.010 | -1.966 | - | - | - | - |
| B_ACL_110_3_6 | Line | B3 -> B6 | 4.657 | 7.235 | -4.637 | -9.159 | 0.019 | -1.923 | - | - | - | - |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Control
-------
Transformer controls: noneThe three connection models of Examples 3.1 to 3.3 side by side. The losses differ because the flow pattern differs; a negative Q loss means the line charging produces more reactive power than the flows consume.
println(rpad("scenario", 20), lpad("Vm(B1) pu", 11), lpad("P loss MW", 11), lpad("Q loss MVAr", 13), " balanced by")
for (label, net, by) in (
("ideal slack", net_slack, "slack bus B1"),
("non-ideal source", net_source, "hidden source bus"),
("distributed slack", net_dist, "B3 (α=0.6) + B6 (α=0.4)"),
)
pl, ql = getTotalLosses(net = net)
println(rpad(label, 20), lpad(string(round(get_bus_vm_pu(net, "B1"); digits = 4)), 11), lpad(string(round(pl; digits = 3)), 11), lpad(string(round(ql; digits = 3)), 13), " ", by)
endscenario Vm(B1) pu P loss MW Q loss MVAr balanced by
ideal slack 1.02 0.821 -15.938 slack bus B1
non-ideal source 1.0087 0.974 -14.086 hidden source bus
distributed slack 1.02 1.537 -12.415 B3 (α=0.6) + B6 (α=0.4)Example 3.4: the IEC 60909 fault sweep. The feeder data declared in addExternalGrid! feeds runShortCircuit! directly, here on the ideal-slack net of Example 3.1. $I_k''$ is largest at the connection bus and decays with electrical distance.
printShortCircuitResult(runShortCircuit!(net_slack; case = :max))
printShortCircuitResult(runShortCircuit!(net_slack; case = :min))Balanced 3-phase short circuit (IEC 60909-0) — case: max, c per IEC Table 1
bus Un[kV] Ik''[kA] Sk''[MVA] kappa ip[kA] status flagged
B1 110.0 10.497 2000.0 2.0 29.691 ok no
B2 110.0 5.703 1086.6 1.931 15.574 ok no
B3 110.0 3.8549 734.47 1.8862 10.283 ok no
B4 110.0 2.9546 562.94 1.8565 7.7573 ok no
B5 110.0 2.978 567.39 1.8597 7.8324 ok no
B6 110.0 3.6897 702.97 1.8749 9.7832 ok no
B7 110.0 4.8744 928.69 1.908 13.153 ok no
B8 110.0 5.5627 1059.8 1.9266 15.156 ok no
Balanced 3-phase short circuit (IEC 60909-0) — case: min, c per IEC Table 1
bus Un[kV] Ik''[kA] Sk''[MVA] kappa ip[kA] status flagged
B1 110.0 7.873 1500.0 2.0 22.268 ok no
B2 110.0 4.649 885.76 1.9386 12.746 ok no
B3 110.0 3.2515 619.5 1.8944 8.7112 ok no
B4 110.0 2.535 482.98 1.8643 6.6835 ok no
B5 110.0 2.5539 486.58 1.8675 6.7448 ok no
B6 110.0 3.1218 594.79 1.8835 8.3154 ok no
B7 110.0 4.0342 768.62 1.9165 10.934 ok no
B8 110.0 4.5462 866.17 1.9344 12.437 ok noChapter 4: transformer tap control (OLTC)
Example 4.1: an OLTC holding a remote bus. A transformer with a ratio tap changer holds the voltage at a remote load bus. The outer control loop moves the discrete tap until the target is inside the deadband; the power flow itself stays untouched. Details: Control Framework.
(slack)
Slack ==T1== MV -------- Load T1: ratio taps 0.9..1.1,
60 MW / 20 MVAr step 0.0125function build_oltc()
net = Net(name = "tour_oltc", baseMVA = 100.0)
addBus!(net = net, busName = "Slack", vn_kV = 110.0)
addBus!(net = net, busName = "MV", vn_kV = 110.0)
addBus!(net = net, busName = "Load", vn_kV = 110.0)
addProsumer!(net = net, busName = "Slack", type = "EXTERNALNETWORKINJECTION", referencePri = "Slack", vm_pu = 1.0, va_deg = 0.0)
addProsumer!(net = net, busName = "Load", type = "ENERGYCONSUMER", p = 60.0, q = 20.0)
addPIModelTrafo!(net = net, fromBus = "Slack", toBus = "MV", r_pu = 0.004, x_pu = 0.06, b_pu = 0.0, ratio = 1.0, shift_deg = 0.0, status = 1)
addPIModelACLine!(net = net, fromBus = "MV", toBus = "Load", r_pu = 0.02, x_pu = 0.10, b_pu = 0.01, status = 1)
# enable the ratio tap on the transformer branch and give it a name the
# controller can address
t = getNetBranch(net = net, fromBus = "Slack", toBus = "MV")
t.comp.cName = "T1"
t.has_ratio_tap = true
t.tap_min = 0.90
t.tap_max = 1.10
t.tap_step = 0.0125
ok, msg = validate!(net = net)
ok || error("Network validation failed: $msg")
return net
end
net_oltc = build_oltc()
run_sparlectra(net = net_oltc)
println("uncontrolled: Vm(Load) = ", round(get_bus_vm_pu(net_oltc, "Load"); digits = 4), " pu")uncontrolled: Vm(Load) = 0.9474 puNow attach the controller (voltage mode, discrete steps) and rerun. run_sparlectra executes the outer control loop automatically when controllers are present.
addTapController!(
net_oltc;
trafo = "T1",
mode = :voltage,
target_bus = "Load",
target_vm_pu = 1.0,
control_ratio = true,
control_phase = false,
is_discrete = true,
deadband_vm_pu = 5e-3,
)
run_sparlectra(net = net_oltc)
println("controlled: Vm(Load) = ", round(get_bus_vm_pu(net_oltc, "Load"); digits = 4), " pu")
printTapControllerSummary(stdout, net_oltc)controlled: Vm(Load) = 1.0036 pu
Transformer Control Summary
---------------------------
Power sign convention: achieved_p_mw is positive in the configured target branch direction (from -> to).
T1 OLTC (T1, 1 -> 2)
controller type : OLTC
mode : voltage
target bus : Load
target Vm : 1.0000 pu
achieved Vm : 1.0036 pu
target branch : -
target P : -
achieved P : -
tap ratio : 0.95000
phase shift : 0.00000 deg
tap position : -4
phase position : +0
ratio range : 0.90000 .. 1.10000
ratio step : 0.01250
phase range : -30.00000 .. 30.00000 deg
phase step : 1.25000 deg
discrete : true
converged : true
at_limit : false
status : convergedReading aid (Example 4.1): the summary shows the chosen tap position and the achieved voltage. With a discrete 0.0125 step the controller stops as soon as the target is inside the deadband, not at the exact setpoint.
Chapter 5: voltage-dependent reactive power, Q(U)
A machine can follow a Q(U) droop characteristic: absorb reactive power when its voltage is high, inject when it is low. Unlike the outer-loop controllers above, Q(U) is solved inside Newton-Raphson. Details: Voltage Dependent Control.
Both examples of this chapter run one three-bus feeder with the Q(U) machine in the middle; only the load at B3 changes:
(slack, 1.02 pu)
B1 -------- B2 -------- B3
Q(U) machine load (light in Example 5.1,
10 MW heavy in Example 5.2)function build_qu(p_load::Float64, q_load::Float64)
net = Net(name = "tour_qu", baseMVA = 100.0)
addBus!(net = net, busName = "B1", vn_kV = 110.0)
addBus!(net = net, busName = "B2", vn_kV = 110.0)
addBus!(net = net, busName = "B3", vn_kV = 110.0)
addProsumer!(net = net, busName = "B1", type = "EXTERNALNETWORKINJECTION", referencePri = "B1", vm_pu = 1.02, va_deg = 0.0)
addPIModelACLine!(net = net, fromBus = "B1", toBus = "B2", r_pu = 0.01, x_pu = 0.08, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net, fromBus = "B2", toBus = "B3", r_pu = 0.01, x_pu = 0.08, b_pu = 0.0, status = 1)
qu = QUController(
make_characteristic(
[(104.5, 30.0), (107.0, 20.0), (110.0, 0.0), (112.0, -10.0), (115.5, -20.0)];
voltage_unit = :kV,
value_unit = :MVAr,
vn_kV = 110.0,
sbase_MVA = 100.0,
interpolation = :polynomial,
);
qmin_MVAr = -50.0,
qmax_MVAr = 50.0,
sbase_MVA = 100.0,
)
addProsumer!(net = net, busName = "B2", type = "SYNCHRONOUSMACHINE", p = 10.0, q = 0.0, qu_controller = qu)
addProsumer!(net = net, busName = "B3", type = "ENERGYCONSUMER", p = p_load, q = q_load)
ok, msg = validate!(net = net)
ok || error("Network validation failed: $msg")
return net
endbuild_qu (generic function with 1 method)Example 5.1: light load. The machine bus sits above 110 kV, so the characteristic asks the machine to absorb reactive power (negative Q).
net_qu_light = build_qu(5.0, 1.0)
etime, ite = solve!(net_qu_light)
printACPFlowResults(net_qu_light, etime, ite, 1e-8)================================================================================
| SPARLECTRA Version 0.9.19 - AC Power Flow Results |
================================================================================
Date : 25-Aug-26 16:19:18
Iterations : 4
Flatstart : No
Tolerance : 1.0e-08
Solver : NR
Total time : 0.000386 s
Case : tour_qu
Cooldown iters : 0
Q-hysteresis : 0.0000 pu
Jacobian cond. : kappa1(J) = 11.2, attainable accuracy ~ 2.5e-15, well conditioned (tol 1.0e-8 reachable)
BaseMVA : 100
Nodes : 3 (PV: 0 PQ: 2 Slack: 1)
Grid connection: slack bus B1
Branches : 2
Links : 0
HVDC links : 0
Lines : 2
Trafos : 0
Generators : 2
Loads : 1
Shunts : 0
Controllers : 1 (Tap: 0, Q(U): 1, P(U): 0)
PV→PQ locks : 0
PV→PQ events : 0
total network power balance (Σ S_branch): P = 0.013 [MW], Q = 0.101 [MVar]
==========================================================================================================================================================================================================================
| Nr | Bus | Vn [kV] | V [kV] | V [pu] | phi [deg] | Pg [MW] | Qg [MVar] | Pl [MW] | Ql [MVar] | Ps [MW] | Qs [MVar] | Type | Control | Tap Vm tgt |
==========================================================================================================================================================================================================================
| 1 | B1 | 110.0 | 112.200 | 1.020 | 0.000 | -4.987 | 8.930 | | | | | SLACK | - | |
| 2 | B2 | 110.0 | 111.485 | 1.013 | 0.271 | 10.000 | -7.829 | | | | | PQ | Q(U) | |
| 3 | B3 | 110.0 | 111.343 | 1.012 | 0.053 | | | 5.000 | 1.000 | | | PQ | - | |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
==========================================================================================================================================================================================================================
| Branch | Type | Connection | P [MW] | Q [MVar] | P [MW] | Q [MVar] | Pv [MW] | Qv [MVar] | Ctrl | P_tgt | TapPos | Ctrl status |
==========================================================================================================================================================================================================================
| B_ACL_110_1_2 | Line | B1 -> B2 | -4.987 | 8.930 | 4.997 | -8.849 | 0.010 | 0.080 | - | - | - | - |
| B_ACL_110_2_3 | Line | B2 -> B3 | 5.003 | 1.020 | -5.000 | -1.000 | 0.003 | 0.020 | - | - | - | - |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Control
-------
Transformer controls: noneExample 5.2: heavy load. The voltage sags below 110 kV and the same characteristic turns the machine into a reactive power injector (positive Q).
net_qu_heavy = build_qu(80.0, 25.0)
etime, ite = solve!(net_qu_heavy)
printACPFlowResults(net_qu_heavy, etime, ite, 1e-8)================================================================================
| SPARLECTRA Version 0.9.19 - AC Power Flow Results |
================================================================================
Date : 25-Aug-26 16:19:18
Iterations : 5
Flatstart : No
Tolerance : 1.0e-08
Solver : NR
Total time : 0.000260 s
Case : tour_qu
Cooldown iters : 0
Q-hysteresis : 0.0000 pu
Jacobian cond. : kappa1(J) = 14.2, attainable accuracy ~ 3.2e-15, well conditioned (tol 1.0e-8 reachable)
BaseMVA : 100
Nodes : 3 (PV: 0 PQ: 2 Slack: 1)
Grid connection: slack bus B1
Branches : 2
Links : 0
HVDC links : 0
Lines : 2
Trafos : 0
Generators : 2
Loads : 1
Shunts : 0
Controllers : 1 (Tap: 0, Q(U): 1, P(U): 0)
PV→PQ locks : 0
PV→PQ events : 0
total network power balance (Σ S_branch): P = 1.335 [MW], Q = 10.682 [MVar]
==========================================================================================================================================================================================================================
| Nr | Bus | Vn [kV] | V [kV] | V [pu] | phi [deg] | Pg [MW] | Qg [MVar] | Pl [MW] | Ql [MVar] | Ps [MW] | Qs [MVar] | Type | Control | Tap Vm tgt |
==========================================================================================================================================================================================================================
| 1 | B1 | 110.0 | 112.200 | 1.020 | 0.000 | 71.335 | 29.538 | | | | | SLACK | - | |
| 2 | B2 | 110.0 | 109.039 | 0.991 | -3.068 | 10.000 | 6.144 | | | | | PQ | Q(U) | |
| 3 | B3 | 110.0 | 105.602 | 0.960 | -6.773 | | | 80.000 | 25.000 | | | PQ | - | |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
==========================================================================================================================================================================================================================
| Branch | Type | Connection | P [MW] | Q [MVar] | P [MW] | Q [MVar] | Pv [MW] | Qv [MVar] | Ctrl | P_tgt | TapPos | Ctrl status |
==========================================================================================================================================================================================================================
| B_ACL_110_1_2 | Line | B1 -> B2 | 71.335 | 29.538 | -70.762 | -24.954 | 0.573 | 4.584 | - | - | - | - |
| B_ACL_110_2_3 | Line | B2 -> B3 | 80.762 | 31.098 | -80.000 | -25.000 | 0.762 | 6.098 | - | - | - | - |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Control
-------
Transformer controls: noneReading aid (Examples 5.1 and 5.2): compare the Qg value and the Control column (Q(U)) of bus B2 between the two tables; the sign flips with the voltage level, exactly along the declared characteristic.
Where to go next
The workshop continues in the ADVANCED tour (Expert and Beyond tiers): remote voltage control by a machine, a steerable HVDC link, state estimation, the FACTS devices and their limits, N-1 contingency analysis, and parallel sweeps on Julia threads.
The focused notebooks with the full narrative of single topics:
And the documentation for going further: