The Sparlectra workshop tour

Open in Colab

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

  1. Your first network, built step by step

Beginner

  1. Working with the model: trust, switching, editing, Q-limits

Advanced

  1. Slack types and short-circuit currents
  2. Transformer tap control (OLTC)
  3. 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 warm

Part 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)
end

addPIModelACLine! 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: none

Reading 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
end
build_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.0

Reading 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 round
for 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))
end
x =     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.3e12

Reading 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] MVAr
net_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 vanished

Reading 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")
end
one 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 MW

Reading 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
                        G
function 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
end
build_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: none

Example 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: none

Example 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: none

The 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)
end
scenario              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         no

Chapter 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.0125
function 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 pu

Now 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             : converged

Reading 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
end
build_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: none

Example 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: none

Reading 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: