FACTS flow control: from the TCSC to the UPFC
Level: Expert, companion of the advanced tour's FACTS chapter. Covers the series-reactance controller (TCSC) and builds up to the full UPFC (unified power flow controller), including the DC-link-coupled model that steers a line's active and reactive flow independently.
Note: This workshop was created with AI assistance and is reviewed and curated by the maintainer; it is not a fully machine-generated text.
In a meshed AC network, power does not follow contracts, it follows impedance: parallel paths split the transfer in inverse proportion to their reactances. A TCSC (thyristor controlled series capacitor) exploits exactly that lever: a variable series reactance in one line steers how much flow that corridor carries. In this notebook you build a small loop network with Sparlectra.jl, watch the natural flow split, and then let the SeriesReactanceControl outer-loop controller move the split onto a target, including the honest failure mode when the target is out of reach.
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
Julia compiles each function on first use. This cell loads the package and warms the two paths this notebook exercises, the power-flow solver and the outer control loop with a series-reactance controller, on a tiny throwaway corridor, so the real study runs at full speed.
using Sparlectra
wnet = Net(name = "warmup", baseMVA = 100.0)
for b in ("A", "M", "B")
addBus!(net = wnet, busName = b, vn_kV = 110.0)
end
addProsumer!(net = wnet, busName = "A", type = "EXTERNALNETWORKINJECTION", referencePri = "A", vm_pu = 1.0, va_deg = 0.0)
addProsumer!(net = wnet, busName = "B", type = "ENERGYCONSUMER", p = 10.0, q = 3.0)
addPIModelACLine!(net = wnet, fromBus = "A", toBus = "M", r_pu = 0.01, x_pu = 0.10, b_pu = 0.0, status = 1)
addPIModelACLine!(net = wnet, fromBus = "M", toBus = "B", r_pu = 0.01, x_pu = 0.10, b_pu = 0.0, status = 1)
addPIModelACLine!(net = wnet, fromBus = "A", toBus = "B", r_pu = 0.02, x_pu = 0.20, b_pu = 0.0, status = 1)
addSeriesReactanceControl!(wnet; fromBus = "A", toBus = "M", p_target_mw = 6.0, x_min_pu = 0.05, x_max_pu = 0.2)
t_ctrl = @elapsed run_control!(wnet; controllers = collect_outer_controllers(wnet), pf_config = PowerFlowConfig(method = :rectangular, max_iter = 15, tol = 1e-8), control_config = ControlConfig(max_outer_iterations = 4, trace = false))
println("warm: power flow plus series-reactance control ", round(t_ctrl; digits = 2), " s (first calls compile)")warm: power flow plus series-reactance control 4.99 s (first calls compile)Why a series reactance steers flow
Every branch enters the power flow through its admittance matrix (see the Branch Model page for the derivation):
\[Y_{br} = \begin{bmatrix} \frac{1}{\tau^2}\left(y_{ser} + \frac{y_{shunt}}{2}\right) & -y_{ser}\,\frac{1}{\tau e^{-j\phi}} \\ -y_{ser}\,\frac{1}{\tau e^{j\phi}} & y_{ser} + \frac{y_{shunt}}{2} \end{bmatrix}, \qquad y_{ser} = \frac{1}{R + jX},\]
with $N = 1$ for lines. The TCSC acts purely through $X$ inside $y_{ser}$: every accepted controller step changes one branch stamp and the outer loop re-stamps the Y-bus before the next solve.
For a lossless line the transfer relation
\[P_{12} = \frac{V_1 V_2}{X}\,\sin(\delta_1 - \delta_2)\]
says that a lower series reactance carries more power at a given angle difference. In a loop, flow redistributes between the parallel paths according to their reactance ratio, which is exactly what we are about to watch.
A loop network with two corridors
Example 1: the natural flow split. 80 MW travel from source A to sink B over two parallel corridors. The upper corridor (A to M2 to B) has twice the reactance of the lower one, so it naturally carries only one third of the transfer.
+---- M1 ----+ corridor 1: x = 0.10 per line (TCSC here)
| |
A ----+ +---- B (load 80 MW)
(slack) | |
+---- M2 ----+ corridor 2: x = 0.20 per linefunction build_loop()
net = Net(name = "tcsc_workshop", baseMVA = 100.0)
for b in ("A", "M1", "M2", "B")
addBus!(net = net, busName = b, vn_kV = 110.0)
end
addProsumer!(net = net, busName = "A", type = "EXTERNALNETWORKINJECTION", referencePri = "A", vm_pu = 1.0, va_deg = 0.0)
addProsumer!(net = net, busName = "B", type = "ENERGYCONSUMER", p = 80.0, q = 20.0)
addPIModelACLine!(net = net, fromBus = "A", toBus = "M1", r_pu = 0.01, x_pu = 0.10, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net, fromBus = "M1", toBus = "B", r_pu = 0.01, x_pu = 0.10, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net, fromBus = "A", toBus = "M2", r_pu = 0.02, x_pu = 0.20, b_pu = 0.0, status = 1)
addPIModelACLine!(net = net, fromBus = "M2", toBus = "B", r_pu = 0.02, x_pu = 0.20, b_pu = 0.0, status = 1)
ok, msg = validate!(net = net)
ok || error("Network validation failed: $msg")
return net
end
net = build_loop()
run_sparlectra(net = net)
println("natural split: corridor 1 (A->M1) = ", round(get_branch_p_from_to_mw(net, "A", "M1"); digits = 2), " MW")
println(" corridor 2 (A->M2) = ", round(get_branch_p_from_to_mw(net, "A", "M2"); digits = 2), " MW")natural split: corridor 1 (A->M1) = 54.0 MW
corridor 2 (A->M2) = 27.0 MWReading aid (Example 1): the 2:1 reactance ratio produces the 2:1 flow split, independent of any thermal ratings: the low-reactance corridor attracts the flow.
Attach the TCSC and steer the split
Example 2: steering the split onto a target. addSeriesReactanceControl! registers the controller on the line from A to M2, continuing on the Example 1 network (diagram above). The target of 35 MW needs a visible reactance move: the outer loop measures the branch flow after each converged solve, steps x_pu via secant iteration (the first step is a bounded probe, because the sign of $dP/dX$ depends on the network), and stops inside the 0.5 MW default deadband.
ctrl = addSeriesReactanceControl!(net; fromBus = "A", toBus = "M2", p_target_mw = 35.0, x_min_pu = 0.02, x_max_pu = 0.30)
run_sparlectra(net = net)
println("steered: corridor 2 (A->M2) = ", round(get_branch_p_from_to_mw(net, "A", "M2"); digits = 2), " MW (target 35)")
println(" x_pu moved 0.20 -> ", round(ctrl.x_pu; digits = 4), ", status = ", ctrl.status)steered: corridor 2 (A->M2) = 34.62 MW (target 35)
x_pu moved 0.20 -> 0.0709, status = convergedThe controller row from the last control run (Example 2) and the generic controllable-element view carry the shared vocabulary (actuator, range, quantity, target) that all outer controllers report:
cr = latest_control_result(net)
println("outer loop: status = ", cr.status, ", outer iterations = ", cr.outer_iterations, ", pf solves = ", cr.powerflow_solves)
for row in cr.controllers
println("controller: ", row.controller_name, " achieved ", round(row.achieved_p_mw; digits = 2), " MW of ", row.p_target_mw, " MW, x_pu = ", round(row.x_pu; digits = 4))
end
for e in controllableElements(net)
println("element: ", e.element, " | ", e.device, " | ", e.actuator, " in [", e.actuator_min, ", ", e.actuator_max, "] | ", e.quantity, " @ ", e.target)
endouter loop: status = converged, outer iterations = 4, pf solves = 4
controller: TCSC_A_M2 TCSC achieved 34.62 MW of 35.0 MW, x_pu = 0.0709
element: branch@A-M2 | TCSC (series compensation) | series_x_pu in [0.02, 0.3] | branch_active_power @ A->M2The same information lands in the classical result tables: the "Controllers" line counts the TCSC, and the controlled A->M2 line carries the Ctrl = TCSC marker with its P target and status (the moved reactance is in the Series Reactance Control Summary of the Control footer).
calcNetLosses!(net)
printACPFlowResults(net, 0.0, 1, 1e-8)================================================================================
| SPARLECTRA Version 0.9.19 - AC Power Flow Results |
================================================================================
Date : 25-Aug-26 16:18:58
Iterations : 1
Flatstart : No
Tolerance : 1.0e-08
Solver : NR
Total time : 0.000000 s
Case : tcsc_workshop
Cooldown iters : 0
Q-hysteresis : 0.0000 pu
Jacobian cond. : kappa1(J) = 9.39, attainable accuracy ~ 2.1e-15, well conditioned (tol 1.0e-8 reachable)
BaseMVA : 100
Nodes : 4 (PV: 0 PQ: 3 Slack: 1)
Grid connection: slack bus A
Branches : 4
Links : 0
HVDC links : 0
Lines : 4
Trafos : 0
Generators : 1
Loads : 1
Shunts : 0
Controllers : 1 (Tap: 0, Q(U): 0, P(U): 0, TCSC: 1)
PV→PQ locks : 0
PV→PQ events : 0
total network power balance (Σ S_branch): P = 1.020 [MW], Q = 8.491 [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 | 110.0 | 110.000 | 1.000 | 0.000 | 81.020 | 28.491 | | | | | SLACK | - | |
| 2 | M1 | 110.0 | 107.692 | 0.979 | -2.615 | | | | | | | PQ | - | |
| 3 | M2 | 110.0 | 108.398 | 0.985 | -1.298 | | | | | | | PQ | - | |
| 4 | B | 110.0 | 105.618 | 0.960 | -5.339 | | | 80.000 | 20.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 | A -> M1 | 46.403 | 17.361 | -46.157 | -14.906 | 0.245 | 2.455 | - | - | - | - |
| B_ACL_110_2_4 | Line | M1 -> B | 46.157 | 14.906 | -45.912 | -12.452 | 0.245 | 2.455 | - | - | - | - |
| B_ACL_110_1_3 | Line | A -> M2 | 34.617 | 11.131 | -34.353 | -10.193 | 0.264 | 0.938 | TCSC | 35.000 | - | converged |
| B_ACL_110_3_4 | Line | M2 -> B | 34.353 | 10.193 | -34.088 | -7.548 | 0.264 | 2.644 | - | - | - | - |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Control
-------
Transformer controls: none
Series Reactance Control Summary (TCSC)
---------------------------------------
TCSC_A_M2 TCSC (branch A -> M2)
target P : 35.000 MW
achieved P : 34.617 MW
series reactance : 0.07092 pu
reactance range : 0.02000 .. 0.30000 pu
deadband : 0.500 MW
converged : true
at_limit : false
status : convergedThe honest limit
Example 3: the honest limit. Ask the same corridor for 70 MW, on a fresh copy of the Example 1 loop (diagram there), and the range [0.02, 0.30] is not enough: the reactance clamps at the capacitive end, the branch behaves as a fixed compensated line, and the controller reports at_limit instead of pretending convergence. The power flow itself stays valid.
net2 = build_loop()
ctrl2 = addSeriesReactanceControl!(net2; fromBus = "A", toBus = "M2", p_target_mw = 70.0, x_min_pu = 0.02, x_max_pu = 0.30)
run_sparlectra(net = net2)
println("limited: corridor 2 (A->M2) = ", round(get_branch_p_from_to_mw(net2, "A", "M2"); digits = 2), " MW (target 70)")
println(" x_pu = ", round(ctrl2.x_pu; digits = 4), " (clamped), at_limit = ", ctrl2.at_limit, ", converged = ", ctrl2.converged)limited: corridor 2 (A->M2) = 38.93 MW (target 70)
x_pu = 0.02 (clamped), at_limit = true, converged = falseIn the classical result the honest limit is visible in the branch row: the A->M2 line's Ctrl status reads "atlimitnot_converged" instead of pretending convergence.
calcNetLosses!(net2)
printACPFlowResults(net2, 0.0, 1, 1e-8)================================================================================
| SPARLECTRA Version 0.9.19 - AC Power Flow Results |
================================================================================
Date : 25-Aug-26 16:19:1
Iterations : 1
Flatstart : No
Tolerance : 1.0e-08
Solver : NR
Total time : 0.000000 s
Case : tcsc_workshop
Cooldown iters : 0
Q-hysteresis : 0.0000 pu
Jacobian cond. : kappa1(J) = 13.8, attainable accuracy ~ 3.1e-15, well conditioned (tol 1.0e-8 reachable)
BaseMVA : 100
Nodes : 4 (PV: 0 PQ: 3 Slack: 1)
Grid connection: slack bus A
Branches : 4
Links : 0
HVDC links : 0
Lines : 4
Trafos : 0
Generators : 1
Loads : 1
Shunts : 0
Controllers : 1 (Tap: 0, Q(U): 0, P(U): 0, TCSC: 1)
PV→PQ locks : 0
PV→PQ events : 0
total network power balance (Σ S_branch): P = 1.067 [MW], Q = 7.701 [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 | 110.0 | 110.000 | 1.000 | 0.000 | 81.067 | 27.701 | | | | | SLACK | - | |
| 2 | M1 | 110.0 | 107.845 | 0.980 | -2.369 | | | | | | | PQ | - | |
| 3 | M2 | 110.0 | 108.893 | 0.990 | -0.318 | | | | | | | PQ | - | |
| 4 | B | 110.0 | 105.882 | 0.963 | -4.830 | | | 80.000 | 20.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 | A -> M1 | 42.141 | 16.211 | -41.937 | -14.172 | 0.204 | 2.039 | - | - | - | - |
| B_ACL_110_2_4 | Line | M1 -> B | 41.937 | 14.172 | -41.733 | -12.133 | 0.204 | 2.039 | - | - | - | - |
| B_ACL_110_1_3 | Line | A -> M2 | 38.926 | 11.491 | -38.596 | -11.161 | 0.329 | 0.329 | TCSC | 70.000 | - | at_limit_not_converged |
| B_ACL_110_3_4 | Line | M2 -> B | 38.596 | 11.161 | -38.267 | -7.867 | 0.329 | 3.294 | - | - | - | - |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Control
-------
Transformer controls: none
Series Reactance Control Summary (TCSC)
---------------------------------------
TCSC_A_M2 TCSC (branch A -> M2)
target P : 70.000 MW
achieved P : 38.926 MW
series reactance : 0.02000 pu
reactance range : 0.02000 .. 0.30000 pu
deadband : 0.500 MW
converged : false
at_limit : true
status : at_limit
status detail : target not reached, reactance clamped at the range end (fixed compensated line)From series reactance to the UPFC
The TCSC above steers flow by changing a series REACTANCE: its injected voltage is in quadrature with the line current, so it exchanges no active power. A UPFC (unified power flow controller) removes that restriction. It adds a SHUNT converter at one line end and couples the two converters through a DC link, so the SERIES converter may inject a voltage of ARBITRARY phase. The in-phase component now carries active power, balanced through the DC link by the shunt, and that is the extra degree of freedom: the line can hold INDEPENDENT active and reactive targets at once.
bus i (from) series converter bus j (to)
V_i o----+---[ + V_se - ]---[ line ]---o V_j
| controlled flow ->
[ shunt conv ] P_sh = -P_se (DC balance) + Q_sh
|
=== DC link === (couples the two converters)Example 4: the quadrature composite (SSSC + STATCOM). In the quadrature limit P_se = 0, the DC link idles, and the UPFC is exactly an SSSC on the branch plus a STATCOM at the bus. addUpfcControl! (default model = :quadrature) registers that pair as one device, on the loop of Example 1 (diagram above) with a machine added at M2 for the shunt converter:
netu = build_loop()
addProsumer!(net = netu, busName = "M2", type = "GENERATOR", p = 0.0, q = 0.0)
upfc = addUpfcControl!(netu; fromBus = "A", toBus = "M2", shunt_bus = "M2",
target_bus = "B", target_vm_pu = 0.99, p_target_mw = 35.0,
v_inj_max_pu = 0.08, s_max_mva = 40.0)
run_control!(netu)
println("quadrature UPFC: two converter rows for one device:")
for row in controllableElements(netu)
println(" ", rpad(row.device, 48), row.actuator, ", at_limit = ", row.at_limit)
endquadrature UPFC: two converter rows for one device:
UPFC shunt (VSC pair, stationary quadrature model)machine_q_mvar, at_limit = true
UPFC series (VSC pair, stationary quadrature model)series_x_pu, at_limit = falseThe classical result tables show the composite as its two sub-controllers: the series side under the TCSC/SSSC summary, the shunt side under the machine (STATCOM) summary, both counted on the "Controllers" line.
calcNetLosses!(netu)
printACPFlowResults(netu, 0.0, 1, 1e-8)================================================================================
| SPARLECTRA Version 0.9.19 - AC Power Flow Results |
================================================================================
Date : 25-Aug-26 16:19:2
Iterations : 1
Flatstart : No
Tolerance : 1.0e-08
Solver : NR
Total time : 0.000000 s
Case : tcsc_workshop
Cooldown iters : 0
Q-hysteresis : 0.0000 pu
Jacobian cond. : kappa1(J) = 9.13, attainable accuracy ~ 2.0e-15, well conditioned (tol 1.0e-8 reachable)
BaseMVA : 100
Nodes : 4 (PV: 0 PQ: 3 Slack: 1)
Grid connection: slack bus A
Branches : 4
Links : 0
HVDC links : 0
Lines : 4
Trafos : 0
Generators : 2
Loads : 1
Shunts : 0
Controllers : 2 (Tap: 0, Q(U): 0, P(U): 0, MachV: 1, TCSC: 1)
PV→PQ locks : 0
PV→PQ events : 0
total network power balance (Σ S_branch): P = 1.091 [MW], Q = 8.453 [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 | 110.0 | 110.000 | 1.000 | 0.000 | 81.091 | -11.852 | | | | | SLACK | - | |
| 2 | M1 | 110.0 | 108.327 | 0.985 | -2.625 | | | | | | | PQ | - | |
| 3 | M2 | 110.0 | 110.840 | 1.008 | -1.471 | | 40.305 | | | | | PQ | - | |
| 4 | B | 110.0 | 106.888 | 0.972 | -5.326 | | | 80.000 | 20.000 | | | PQ | STATCOM | |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
==========================================================================================================================================================================================================================
| 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 | A -> M1 | 46.256 | 11.619 | -46.028 | -9.345 | 0.227 | 2.275 | - | - | - | - |
| B_ACL_110_2_4 | Line | M1 -> B | 46.028 | 9.345 | -45.801 | -7.070 | 0.227 | 2.275 | - | - | - | - |
| B_ACL_110_1_3 | Line | A -> M2 | 34.835 | -23.471 | -34.482 | 24.544 | 0.353 | 1.073 | TCSC | 35.000 | - | converged |
| B_ACL_110_3_4 | Line | M2 -> B | 34.482 | 15.762 | -34.199 | -12.930 | 0.283 | 2.832 | - | - | - | - |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Control
-------
Transformer controls: none
Machine Voltage Control Summary
-------------------------------
UPFC_A_M2_shunt STATCOM (machine at M2 -> bus B)
target Vm : 0.9900 pu
achieved Vm : 0.9717 pu
reactive output : 40.305 MVAr
limit mode : STATCOM current limit, S_max = 40.000 MVA at 1.0 pu
live Q range : -40.305 .. 40.305 MVAr (at Vt = 1.0076 pu)
deadband : 0.0010 pu
converged : false
at_limit : true
status : at_limit
status detail : target not reached because a reactive limit was hit
Series Reactance Control Summary (TCSC)
---------------------------------------
UPFC_A_M2_series SSSC (branch A -> M2)
target P : 35.000 MW
achieved P : 34.835 MW
series reactance : 0.06080 pu
limit mode : SSSC injected-voltage limit, V_inj,max = 0.0800 pu
live x window : 0.00954 .. 0.39046 pu (x_base 0.20000, |I| = 0.4200 pu)
injected voltage : 0.0585 pu (of 0.0800 pu available)
deadband : 0.500 MW
converged : true
at_limit : false
status : convergedReading aid (Example 4): one call, one composite name, but honestly TWO result rows, the series (SSSC) and the shunt (STATCOM), each with its own at_limit. This is the whole device in the quadrature limit; it steers ONE line quantity plus the shunt voltage, but not independent P and Q.
Example 5: the full model, independent P and Q. Lifting the quadrature restriction (model = :full) lets the series converter inject an arbitrary-phase voltage; the line then holds distinct P and Q targets, with the active part balanced across the DC link. The full model needs the shunt at the SENDING bus, so a small mesh (the parallel S->L path lets the flow be steered):
S (slack) --- I ==[UPFC series]== J --- L (load)
| S ------------- L
shunt converter at I (parallel path)function build_upfc_mesh()
m = Net(name = "upfc_mesh", baseMVA = 100.0)
for b in ("S", "I", "J", "L")
addBus!(net = m, busName = b, vn_kV = 110.0)
end
addProsumer!(net = m, busName = "S", type = "EXTERNALNETWORKINJECTION", referencePri = "S", vm_pu = 1.0, va_deg = 0.0)
addProsumer!(net = m, busName = "I", type = "GENERATOR", p = 0.0, q = 0.0) # shunt converter
addProsumer!(net = m, busName = "L", type = "ENERGYCONSUMER", p = 90.0, q = 30.0)
addPIModelACLine!(net = m, fromBus = "S", toBus = "I", r_pu = 0.01, x_pu = 0.08, b_pu = 0.0, status = 1)
addPIModelACLine!(net = m, fromBus = "I", toBus = "J", r_pu = 0.02, x_pu = 0.18, b_pu = 0.0, status = 1)
addPIModelACLine!(net = m, fromBus = "J", toBus = "L", r_pu = 0.01, x_pu = 0.08, b_pu = 0.0, status = 1)
addPIModelACLine!(net = m, fromBus = "S", toBus = "L", r_pu = 0.02, x_pu = 0.16, b_pu = 0.0, status = 1)
ok, msg = validate!(net = m)
ok || error("mesh net invalid: $msg")
return m
end
netf = build_upfc_mesh()
full = addUpfcControl!(netf; model = :full, fromBus = "I", toBus = "J", shunt_bus = "I",
p_target_mw = 40.0, q_target_mvar = 10.0, q_shunt_mvar = 0.0,
v_inj_max_pu = 0.30, s_max_mva = 120.0,
deadband_p_mw = 1e-2, deadband_q_mvar = 1e-2, max_outer_iters = 80)
run_control!(netf; control_config = ControlConfig(max_outer_iterations = 80))
f = full.upfc
println("full UPFC on I->J:")
println(" line P = ", round(f.achieved_p_mw; digits = 2), " MW (target 40) and Q = ", round(f.achieved_q_mvar; digits = 2), " MVAr (target 10), both at once")
println(" series V_se = ", round(abs(f.v_se_pu); digits = 4), " pu, P_se = ", round(f.p_se_mw; digits = 3), " MW")
println(" DC-link balance P_se + P_sh = ", round(f.p_se_mw + f.p_sh_mw; digits = 4), " MW")full UPFC on I->J:
line P = 40.0 MW (target 40) and Q = 9.99 MVAr (target 10), both at once
series V_se = 0.0552 pu, P_se = 0.631 MW
DC-link balance P_se + P_sh = 0.0 MWThe classical result tables carry the whole picture: the "Controllers" line now counts the UPFC, and the "UPFC Control Summary" block reports the line P/Q targets vs achieved, the series voltage, and the DC-link residual.
calcNetLosses!(netf)
printACPFlowResults(netf, 0.0, 1, 1e-8)================================================================================
| SPARLECTRA Version 0.9.19 - AC Power Flow Results |
================================================================================
Date : 25-Aug-26 16:19:2
Iterations : 1
Flatstart : No
Tolerance : 1.0e-08
Solver : NR
Total time : 0.000000 s
Case : upfc_mesh
Cooldown iters : 0
Q-hysteresis : 0.0000 pu
Jacobian cond. : kappa1(J) = 17.5, attainable accuracy ~ 3.9e-15, well conditioned (tol 1.0e-8 reachable)
BaseMVA : 100
Nodes : 4 (PV: 0 PQ: 3 Slack: 1)
Grid connection: slack bus S
Branches : 4
Links : 0
HVDC links : 0
Lines : 4
Trafos : 0
Generators : 2
Loads : 1
Shunts : 0
Controllers : 1 (Tap: 0, Q(U): 0, P(U): 0, UPFC: 1)
PV→PQ locks : 0
PV→PQ events : 0
total network power balance (Σ S_branch): P = 2.026 [MW], Q = 9.299 [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 | S | 110.0 | 110.000 | 1.000 | 0.000 | 92.657 | 39.299 | | | | | SLACK | - | |
| 2 | I | 110.0 | 108.601 | 0.987 | -1.829 | -0.631 | | | | | | PQ | - | |
| 3 | J | 110.0 | 105.523 | 0.959 | -2.771 | | | | | | | PQ | - | |
| 4 | L | 110.0 | 104.302 | 0.948 | -4.681 | | | 90.000 | 30.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 | S -> I | 40.813 | 11.428 | -40.633 | -9.991 | 0.180 | 1.437 | - | - | - | - |
| B_ACL_110_2_3 | Line | I -> J | 40.002 | 9.991 | -39.023 | -9.067 | 0.979 | 0.924 | UPFC | 40.000 | - | converged |
| B_ACL_110_3_4 | Line | J -> L | 39.023 | 9.067 | -38.849 | -7.672 | 0.174 | 1.395 | - | - | - | - |
| B_ACL_110_1_4 | Line | S -> L | 51.844 | 27.871 | -51.151 | -22.328 | 0.693 | 5.543 | - | - | - | - |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Control
-------
Transformer controls: none
UPFC Control Summary (full, DC-link coupled)
-------------------------------------------
UPFC_I_J UPFC (line I -> J, shunt at I)
line P target/achieved : 40.000 / 40.002 MW
line Q target/achieved : 10.000 / 9.991 MVar
series voltage V_se : 0.0552 pu at -90.0 deg
series/shunt active : P_se 0.631 MW, P_sh -0.631 MW
DC-link residual : 0.0004 MW
shunt reactive Q_sh : 0.000 MVar (bound +-120.0)
injected-voltage limit : 0.3000 pu
converged / at_limit : true / false
status : convergedForcing the series phase back to quadrature collapses P_se to zero, back to the Example 4 behaviour:
netfq = build_upfc_mesh()
fq = addUpfcControl!(netfq; model = :full, series_phase = :quadrature, fromBus = "I", toBus = "J",
shunt_bus = "I", p_target_mw = 40.0, q_target_mvar = 0.0, q_shunt_mvar = 0.0,
v_inj_max_pu = 0.30, s_max_mva = 120.0, deadband_p_mw = 1e-2, max_outer_iters = 80)
run_control!(netfq; control_config = ControlConfig(max_outer_iterations = 80))
println("quadrature-forced: P_se = ", round(fq.upfc.p_se_mw; digits = 4), " MW (zero: no phase-shifter DOF)")quadrature-forced: P_se = 0.0 MW (zero: no phase-shifter DOF)Reading aid (Example 5): the number that unlocks independent P and Q is P_se, the active power the series converter pushes through the DC link (nonzero here, exactly zero when forced to quadrature). First-cut honesty: the shunt runs on a reactive setpoint (closed-loop shunt voltage is a follow-up), and the model converges for feasible, moderate targets. The full limitation list and the phasor picture are on the FACTS Devices page.
Where to go next
- FACTS Devices: the device taxonomy (STATCOM, SVC, SSSC, TCSC, both UPFC models), the limit-characteristic comparison, and the UPFC phasor picture.
- Series Compensation (TCSC): the theory page, compensation degree, device versus model, and the resonance guard.
- Control Framework: the outer loop all controllers share, and the uniform element view.
- Workshop tour: all workshop examples in one Colab session.