HVDC Converters (VSC & LCC)

To follow along, you can download this tutorial as a Julia script (.jl) or Jupyter notebook (.ipynb).

An HVDC link moves power between two AC buses through a DC stage: an AC/DC converter at each end, joined by a DC line. PowerFlows co-solves this DC stage together with the surrounding AC network, so an AC power flow with an HVDC branch converges to one consistent solution rather than being solved as two separate problems. PowerFlows represents two converter technologies:

  • VSC (voltage-source converter): self-commutated, so each terminal independently controls its own active power (or DC voltage) and reactive power (or AC voltage).
  • LCC (line-commutated converter): thyristor-based, so the only controls are each terminal's firing/extinction angle and transformer tap, and the converter always draws reactive power from the AC system it connects to.

This tutorial builds one 14-bus system for each technology and solves them with the same ACPowerFlow solver used elsewhere in these docs.

Loading the needed packages

using PowerSystemCaseBuilder
using PowerSystems
using PowerFlows

VSC case

Build the 14-bus test system with a single PowerSystems.TwoTerminalVSCLine replacing the AC line on the bus 2↔3 arc:

sys_vsc = build_system(
    PSITestSystems,
    "c_sys14_hvdc_vsc";
    force_build = true,
    add_forecasts = false,
)
System
PropertyValue
Name
Description
System Units BaseSYSTEM_BASE
Base Power100.0
Base Frequency60.0
Num Components70
Static Components
TypeCount
ACBus14
Arc20
Line15
PowerLoad11
TapTransformer3
ThermalStandard5
Transformer2W1
TwoTerminalVSCLine1

Solve it with ACPowerFlow, the same convenience wrapper used for a plain AC-only system — no special solver settings are needed for a VSC line:

vsc_results = solve_power_flow(ACPowerFlow(), sys_vsc)
Dict{String, DataFrame} with 7 entries:
  "flow_results" => 19×10 DataFrame
 Row │ flow_name  bus_from  bus_to  P_from_to      Q_from_to   P_to_from       Q_to_from   P_losses      Q_losses    angle_difference
     │ String     Int64     Int64   Float64        Float64     Float64         Float64     Float64       Float64     Float64
─────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1 │ 1-2               1       2  151.206        -19.0678    -147.217         25.3951     3.98818      6.32733          0.0836842
   2 │ 1-5               1       5   83.4989         5.1547     -80.1161         3.5094     3.38278      8.6641           0.169872
   3 │ 2-4               2       4   71.8485        -1.92205    -69.1016         6.65797    2.74697      4.73593          0.120062
   4 │ 2-5               2       5   53.3743         0.688583   -51.8851         0.185282   1.48914      0.873864         0.0861876
   5 │ 3-4               3       4  -54.2           22.4956      56.4817       -17.9808     2.28172      4.51474         -0.10602
   6 │ 4-5               4       5  -76.768         18.6542      77.5809       -16.0902     0.812856     2.564           -0.0338745
   7 │ 4-7               4       7   26.4901        -6.33111    -26.4901         7.77863    3.33067e-14  1.44753          0.0510156
   8 │ 4-9               4       9   15.0978         2.89982    -15.0978        -1.69565   -5.55112e-15  1.20417          0.0779145
   9 │ 5-6               5       6   46.8203        10.7953     -46.8203        -5.89344    2.22045e-14  4.90188          0.101394
  10 │ 6-11              6      11    8.87323        8.19859     -8.75215       -7.94503    0.12108      0.253556         0.00880368
  11 │ 6-12              6      12    8.12053        3.07436     -8.03959       -2.9059     0.0809395    0.168458         0.0150775
  12 │ 6-13              6      13   18.6266         9.62763    -18.3726        -9.12739    0.254015     0.500235         0.0159768
  13 │ 7-8               7       8   -5.30825e-13  -24.2014       5.30825e-13   25.1383     0.0          0.93692         -8.32667e-16
  14 │ 7-9               7       9   26.4901        16.4227     -26.4901       -15.4522     0.0          0.970468         0.0268988
  15 │ 9-10              9      10    3.80107       -0.219032    -3.79674        0.230521   0.00432521   0.0114895        0.003081
  16 │ 9-14              9      14    8.28684        0.766913    -8.20426       -0.591263   0.0825761    0.17565          0.0203397
  17 │ 10-11            10      11   -5.20324       -6.03052      5.25216        6.14503    0.0489183    0.114512        -0.0046723
  18 │ 12-13            12      13    1.93958        1.3059      -1.92869       -1.29605    0.010884     0.00984746       0.00089934
  19 │ 13-14            13      14    6.80122        4.62352     -6.69574       -4.40876    0.105481     0.214763         0.0147578
  "area_interchange_results" => 0×6 DataFrame
 Row │ area    ni_solved  pdes     delta_p  schedule_status  beyond_limits
     │ String  Float64    Float64  Float64  Symbol           Bool
─────┴─────────────────────────────────────────────────────────────────────
  "bus_results" => 14×9 DataFrame
 Row │ bus_number  Vm       θ           P_gen    P_load   P_net    Q_gen     Q_load   Q_net
     │ Int64       Float64  Float64     Float64  Float64  Float64  Float64   Float64  Float64
─────┼─────────────────────────────────────────────────────────────────────────────────────────
   1 │          1  1.06      0.0        234.704      0.0  234.704  -13.913       0.0  -13.913
   2 │          2  1.045    -0.0836842   40.0       21.7   18.3     31.8617     12.7   19.1617
   3 │          3  1.01     -0.309766     0.0       94.2  -94.2     31.4956     19.0   12.4956
   4 │          4  1.01245  -0.203746     0.0       47.8  -47.8      0.0        -3.9    3.9
   5 │          5  1.01539  -0.169872     0.0        7.6   -7.6      0.0         1.6   -1.6
   6 │          6  1.07     -0.271266     0.0       11.2  -11.2     22.5071      7.5   15.0071
   7 │          7  1.04938  -0.254762     0.0        0.0    0.0      0.0         0.0    0.0
   8 │          8  1.09     -0.254762     0.0        0.0    0.0     25.1383      0.0   25.1383
   9 │          9  1.03253  -0.281661     0.0       29.5  -29.5      0.0        16.6  -16.6
  10 │         10  1.03155  -0.284742     0.0        9.0   -9.0      0.0         5.8   -5.8
  11 │         11  1.04692  -0.280069     0.0        3.5   -3.5      0.0         1.8   -1.8
  12 │         12  1.05344  -0.286343     0.0        6.1   -6.1      0.0         1.6   -1.6
  13 │         13  1.0469   -0.287243     0.0       13.5  -13.5      0.0         5.8   -5.8
  14 │         14  1.02053  -0.302        0.0       14.9  -14.9      0.0         5.0   -5.0
  "mtdc_line_results" => 0×5 DataFrame
 Row │ line_name  dc_bus_from  dc_bus_to  dc_current  P_losses
     │ String     Int64        Int64      Float64     Float64
─────┴─────────────────────────────────────────────────────────
  "vsc_results" => 1×11 DataFrame
 Row │ line_name  bus_from  bus_to  P_from_to  P_to_from  Q_from_to  Q_to_from  dc_current  Vdc_from  Vdc_to   P_losses
     │ String     Int64     Int64   Float64    Float64    Float64    Float64    Float64     Float64   Float64  Float64
─────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1 │ VSC_2_3           2       3    40.2945      -40.0        5.0       10.0    0.383758      1.05  1.04232   0.29454
  "mtdc_results" => 0×8 DataFrame
 Row │ converter_name  ac_bus  dc_bus  P_dc     Q        Vac      Vdc      mode
     │ String          Int64   Int64   Float64  Float64  Float64  Float64  String
─────┴────────────────────────────────────────────────────────────────────────────
  "lcc_results" => 0×13 DataFrame
 Row │ line_name  bus_from  bus_to  rectifier_tap  inverter_tap  rectifier_delay_angle  inverter_extinction_angle  P_from_to  P_to_from  Q_from_to  Q_to_from  P_losses  Q_losses
     │ String     Int64     Int64   Float64        Float64       Float64                Float64                    Float64    Float64    Float64    Float64    Float64   Float64
─────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

The result Dict carries the usual AC keys ("bus_results", "flow_results") plus HVDC-specific tables. For a VSC line, the relevant one is "vsc_results":

vsc_results["vsc_results"]
line_namebus_frombus_toP_from_toP_to_fromQ_from_toQ_to_fromdc_currentVdc_fromVdc_toP_losses
StringInt64Int64Float64Float64Float64Float64Float64Float64Float64Float64
VSC_2_32340.29453966932661-40.05.010.00.38375752066025341.051.0423248495867950.29453966932661

P_from_to is the active power (MW) the from-side AC bus delivers into the converter; Q_from_to/Q_to_from are the reactive power each AC terminal exchanges with its own bus; dc_current, Vdc_from, Vdc_to describe the state of the (here two-node) internal DC network; and P_losses is the combined DC-side loss (converter losses plus the DC line's own resistive drop).

The two converters play different roles. This system's from converter is configured with dc_control_from = VSCDCControlModes.DC_VOLTAGE: it holds the DC network at its scheduled voltage, acting as the DC-side slack. The to converter is configured with dc_control_to = VSCDCControlModes.DC_POWER: it is dispatched to a scheduled active-power transfer, and the from converter's power then adjusts to balance that transfer plus DC losses — which is exactly the relationship between P_from_to and P_losses above. Independently of this DC-side role, each converter also holds its own AC-side reactive-power setpoint (ac_control_from/ac_control_to = VSCACControlModes.AC_REACTIVE_POWER here), so Q_from_to and Q_to_from are unrelated numbers rather than mirror images of each other.

Bus voltages solve normally alongside the DC network:

vsc_results["bus_results"]
4 rows omitted
bus_numberVmθP_genP_loadP_netQ_genQ_loadQ_net
Int64Float64Float64Float64Float64Float64Float64Float64Float64
11.060.0234.704464898826930.0234.70446489882693-13.9130217983424880.0-13.913021798342488
21.045-0.0836841634466739140.021.718.331.8617438416617312.719.161743841661732
31.01-0.30976637501725480.094.19999999999999-94.1999999999999931.49557504179984619.012.495575041799844
41.0124457670918174-0.203746255368650540.047.8-47.80.0-3.93.9
51.0153921142961957-0.16987172500029360.07.6-7.60.01.6-1.6
61.07-0.2712657396947490.011.200000000000001-11.20000000000000122.5071111903158047.515.007111190315802
71.0493750708989091-0.25476190079716490.00.00.00.00.00.0
81.09-0.254761900797164040.00.00.025.138333273338520.025.13833327333852
91.0325320744216007-0.281660724439707740.029.5-29.50.016.6-16.6
101.031545197148625-0.28474172212630940.09.0-9.00.05.800000000000001-5.800000000000001

LCC case

Build the matching system with a single PowerSystems.TwoTerminalLCCLine on the same 2↔3 arc, and solve it the same way:

sys_lcc = build_system(
    PSITestSystems,
    "c_sys14_hvdc_lcc";
    force_build = true,
    add_forecasts = false,
)
lcc_results = solve_power_flow(ACPowerFlow(), sys_lcc)
Dict{String, DataFrame} with 7 entries:
  "flow_results" => 19×10 DataFrame
 Row │ flow_name  bus_from  bus_to  P_from_to      Q_from_to   P_to_from       Q_to_from   P_losses      Q_losses    angle_difference
     │ String     Int64     Int64   Float64        Float64     Float64         Float64     Float64       Float64     Float64
─────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1 │ 1-2               1       2  152.969        -19.4851    -148.886         26.1021     4.08306      6.61703          0.0847029
   2 │ 1-5               1       5   81.1987         5.02285    -77.9991         2.88196    3.19961      7.90481          0.165001
   3 │ 2-4               2       4   67.2199        -1.34845    -64.8154         5.04322    2.40458      3.69477          0.111906
   4 │ 2-5               2       5   49.9663         1.126      -48.6595        -0.811242   1.30675      0.314762         0.0802976
   5 │ 3-4               3       4  -45.3912        17.6128      46.9638       -14.9088     1.5726       2.704           -0.0879452
   6 │ 4-5               4       5  -71.8724        16.967       72.5818       -14.7296     0.709339     2.23747         -0.0316087
   7 │ 4-7               4       7   26.7028        -6.1635     -26.7028         7.62713    5.55112e-15  1.46363          0.0513757
   8 │ 4-9               4       9   15.2212         2.96206    -15.2212        -1.73855    0.0          1.22351          0.0784713
   9 │ 5-6               5       6   46.4769        11.0586     -46.4769        -6.21864   -3.33067e-14  4.84001          0.100585
  10 │ 6-11              6      11    8.66495        8.19544     -8.54694       -7.94832    0.118007     0.247121         0.00843502
  11 │ 6-12              6      12    8.0932         3.07793     -8.01271       -2.91041    0.0804874    0.167517         0.0150112
  12 │ 6-13              6      13   18.5187         9.62347    -18.267         -9.12789    0.251654     0.495585         0.0158528
  13 │ 7-8               7       8   -1.32533e-13  -24.0052       1.32533e-13   24.9264     0.0          0.921191        -2.498e-16
  14 │ 7-9               7       9   26.7028        16.378      -26.7028       -15.3983     0.0          0.979671         0.0270956
  15 │ 9-10              9      10    4.00518       -0.224733    -4.00038        0.237477   0.00479765   0.0127445        0.00324229
  16 │ 9-14              9      14    8.41889        0.761642    -8.33376       -0.580558   0.0851305    0.181084         0.0206716
  17 │ 10-11            10      11   -4.99961       -6.03748      5.04696        6.14832    0.0473495    0.11084         -0.00430264
  18 │ 12-13            12      13    1.9127         1.31041     -1.902         -1.30073    0.010701     0.00968183       0.000841509
  19 │ 13-14            13      14    6.66901        4.6287      -6.56624       -4.41947    0.102764     0.209232         0.0143142
  "area_interchange_results" => 0×6 DataFrame
 Row │ area    ni_solved  pdes     delta_p  schedule_status  beyond_limits
     │ String  Float64    Float64  Float64  Symbol           Bool
─────┴─────────────────────────────────────────────────────────────────────
  "bus_results" => 14×9 DataFrame
 Row │ bus_number  Vm       θ           P_gen    P_load   P_net    Q_gen     Q_load   Q_net
     │ Int64       Float64  Float64     Float64  Float64  Float64  Float64   Float64  Float64
─────┼─────────────────────────────────────────────────────────────────────────────────────────
   1 │          1  1.06      0.0        234.168      0.0  234.168  -14.4622      0.0  -14.4622
   2 │          2  1.045    -0.0847029   40.0       21.7   18.3     46.7101     12.7   34.0101
   3 │          3  1.01     -0.284554     0.0       94.2  -94.2     54.9443     19.0   35.9443
   4 │          4  1.0131   -0.196609     0.0       47.8  -47.8      0.0        -3.9    3.9
   5 │          5  1.01603  -0.165001     0.0        7.6   -7.6      0.0         1.6   -1.6
   6 │          6  1.07     -0.265585     0.0       11.2  -11.2     22.1782      7.5   14.6782
   7 │          7  1.04972  -0.247985     0.0        0.0    0.0      0.0         0.0    0.0
   8 │          8  1.09     -0.247985     0.0        0.0    0.0     24.9264      0.0   24.9264
   9 │          9  1.03293  -0.275081     0.0       29.5  -29.5      0.0        16.6  -16.6
  10 │         10  1.03189  -0.278323     0.0        9.0   -9.0      0.0         5.8   -5.8
  11 │         11  1.04711  -0.27402      0.0        3.5   -3.5      0.0         1.8   -1.8
  12 │         12  1.05346  -0.280596     0.0        6.1   -6.1      0.0         1.6   -1.6
  13 │         13  1.04697  -0.281438     0.0       13.5  -13.5      0.0         5.8   -5.8
  14 │         14  1.0208   -0.295752     0.0       14.9  -14.9      0.0         5.0   -5.0
  "mtdc_line_results" => 0×5 DataFrame
 Row │ line_name  dc_bus_from  dc_bus_to  dc_current  P_losses
     │ String     Int64        Int64      Float64     Float64
─────┴─────────────────────────────────────────────────────────
  "vsc_results" => 0×11 DataFrame
 Row │ line_name  bus_from  bus_to  P_from_to  P_to_from  Q_from_to  Q_to_from  dc_current  Vdc_from  Vdc_to   P_losses
     │ String     Int64     Int64   Float64    Float64    Float64    Float64    Float64     Float64   Float64  Float64
─────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  "mtdc_results" => 0×8 DataFrame
 Row │ converter_name  ac_bus  dc_bus  P_dc     Q        Vac      Vdc      mode
     │ String          Int64   Int64   Float64  Float64  Float64  Float64  String
─────┴────────────────────────────────────────────────────────────────────────────
  "lcc_results" => 1×13 DataFrame
 Row │ line_name  bus_from  bus_to  rectifier_tap  inverter_tap  rectifier_delay_angle  inverter_extinction_angle  P_from_to  P_to_from  Q_from_to  Q_to_from  P_losses  Q_losses
     │ String     Int64     Int64   Float64        Float64       Float64                Float64                    Float64    Float64    Float64    Float64    Float64   Float64
─────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1 │ LCC_2_3           2       3        1.27379       1.35646                    0.0                   0.296706       50.0   -48.8088    8.13037    18.3315   1.19115   26.4618

The matching result table is "lcc_results":

lcc_results["lcc_results"]
line_namebus_frombus_torectifier_tapinverter_taprectifier_delay_angleinverter_extinction_angleP_from_toP_to_fromQ_from_toQ_to_fromP_lossesQ_losses
StringInt64Int64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64
LCC_2_3231.27378588888888381.3564592166774010.00.2967059728390360550.0-48.8088481701515358.13037014672464418.3314777645647451.191151829848463626.46184791128939

An LCC has no independent control over its terminal voltages or reactive power. The rectifier and inverter each have one control angle — rectifier_delay_angle (firing angle) and inverter_extinction_angle (extinction angle) — plus a transformer tap, rectifier_tap/ inverter_tap. PowerFlows keeps both thyristor angles at their minimum physical limits (rectifier_delay_angle_limits/ inverter_extinction_angle_limits on the component) and instead uses the tap ratios to hold the line's scheduled DC power transfer — here the line's transfer_setpoint of 50 MW appears directly as P_from_to.

Because thyristors can only be switched on, not off, on demand, an LCC always draws reactive power from the AC system at both ends to commutate — it cannot supply it. Q_from_to and Q_to_from are both positive (absorbed from the AC network), in contrast to the VSC case above, where each converter's reactive power is an independent setpoint that can be positive, negative, or zero.

lcc_results["bus_results"]
4 rows omitted
bus_numberVmθP_genP_loadP_netQ_genQ_loadQ_net
Int64Float64Float64Float64Float64Float64Float64Float64Float64
11.060.0234.168047970030870.0234.16804797003087-14.4621807232023980.0-14.462180723202398
21.045-0.0847029445508610140.021.718.346.71013384759170612.734.0101338475917
31.01-0.28455445675066560.094.19999999999999-94.1999999999999954.9442501577235719.035.94425015772357
41.0131003362369728-0.19660929082930520.047.8-47.80.0-3.93.9
51.0160263215193588-0.16500058942154840.07.6-7.60.01.6-1.6
61.07-0.265585180306575850.011.200000000000001-11.20000000000000122.178178532468467.514.678178532468461
71.049717526472408-0.247984993224277720.00.00.00.00.00.0
81.09-0.247984993224277470.00.00.024.926424907504690.024.92642490750469
91.032932594115382-0.27508054719192390.029.5-29.50.016.6-16.6
101.0318884362688723-0.27832284098127640.09.0-9.00.05.800000000000001-5.800000000000001

When each is modeled

Reach for VSC when a converter has independent control over active power, reactive power (or AC voltage), and DC voltage — the behavior of modern IGBT-based HVDC links. Reach for LCC to model classic thyristor-based HVDC, where the only controls are firing/extinction angles and transformer taps, and the converter is always a net reactive-power sink on the AC side. PowerFlows solves both inside the same AC power flow as the surrounding network, so no separate DC power-flow step is needed for either technology.