DC OPF

Four electricity zones A–B–C–D sit on an AC ring of maketransmissionlink lines, with one extra diagonal A–C. A single CCGT on A supplies flat 1 MW demand in every zone. Left alone, the mesh is a transport model: a network flow problem that enforces nodal balance (and capacity limits) but not Kirchhoff's voltage law. Calling applydcopf! adds KVL on every independent AC cycle, so the study becomes a DC optimal power-flow approximation of the AC mesh: corridor flows follow the split implied by the line susceptances.

Use transport when nodal balance and transfer limits are enough for the study. Turn on DC OPF when corridor flows must reflect AC network physics—how exchanges split across parallel paths under KVL and the line susceptances. See DC Power Flow for the signed-flow definition and the cycle constraints used by applydcopf!.

This page keeps the same plants and demand and changes only the network:

  1. transport (no applydcopf!, all AC, including an AC diagonal);
  2. DC OPF on the same AC mesh (applydcopf! after the network is built);
  3. DC OPF again, but with the diagonal built as controllable HVDC (dc=true).

After each solve, write_results writes the standard Posy2 workbook report under results/—one workbook per scenario. In Annual values (all), the Interconnection volume tables (total, AC, and DC) summarise annual corridor exchanges. The markdown tables on this page match that workbook section.

Transport (no KVL)

Without applydcopf! there is no need for susceptances. The solver may pick any feasible transport pattern.

using Posy2
using Nosy
using HiGHS
import JuMP: set_silent

# Simulation and Posy2 input configuration
sim = Sim(Model(HiGHS.Optimizer); mesh=TimeMesh())
set_silent(model(sim))
example_data_dir = joinpath(pkgdir(Posy2), "data")
snapshot = Snapshot(sim, Dict(:posy => Posy2Options(
    data_dir=example_data_dir,
    techdata_file="tech_data.xlsx",
    timeseries_file="time_series.xlsx",
    tech_mode=:excel,
    timeseries_mode=:arguments,
)))

# Electricity zones and CO2 sink
a = Node("A", EnergyCarrier("electricity A", sim), rule=:curtailed, evalprice=true, tags=[:electricity])
b = Node("B", EnergyCarrier("electricity B", sim), rule=:curtailed, evalprice=true, tags=[:electricity])
c = Node("C", EnergyCarrier("electricity C", sim), rule=:curtailed, evalprice=true, tags=[:electricity])
d = Node("D", EnergyCarrier("electricity D", sim), rule=:curtailed, evalprice=true, tags=[:electricity])
co2 = Node("CO2", CO2Carrier("CO2", sim), rule=:curtailed, tags=[:co2])

# Flat 1 MW demand in each zone
makedemand("Demand", "A", a, snapshot; profile_multiplier=0.0, annual_flat_demand=1.0 * 8760)
makedemand("Demand", "B", b, snapshot; profile_multiplier=0.0, annual_flat_demand=1.0 * 8760)
makedemand("Demand", "C", c, snapshot; profile_multiplier=0.0, annual_flat_demand=1.0 * 8760)
makedemand("Demand", "D", d, snapshot; profile_multiplier=0.0, annual_flat_demand=1.0 * 8760)

# 10 MW CCGT on A; costs from the technology workbook
makedispatchable("CCGT", a, snapshot; co2_node=co2, tech_column="CCGT", cap=10.0, unit_size=0.0)

# AC ring with a nonbinding 10 MW capacity
maketransmissionlink("IC", a, b, snapshot; cap=10.0)
maketransmissionlink("IC", b, c, snapshot; cap=10.0)
maketransmissionlink("IC", c, d, snapshot; cap=10.0)
maketransmissionlink("IC", d, a, snapshot; cap=10.0)
# AC diagonal A–C
maketransmissionlink("IC", a, c, snapshot; cap=10.0)

# Minimise total system cost and extract solved values
optimize!(snapshot, cost(snapshot))
result_transport = extract(snapshot)
julia> write_results(result_transport, "results/transport.xlsx", overwrite=true)
"results/transport.xlsx"

That creates results/transport.xlsx. The directed corridor volumes below match the Interconnection volume table in Annual values (all) (TWh/y). Every line is AC, so the DC volume block in the workbook is empty.

From \ ToABCDTotal
A0.0030.0120.0110.026
B00.0010.001
C00.0070.0010.007
D00.0030.003
Total00.010.0160.0120.038

Zone A hosts the only plant, so energy is exported from A to the other zones (A -> B, A -> C, A -> D). Without KVL the solver may choose among several feasible transport patterns. The table above is the annual corridor volume for this solve.

DC OPF (with KVL)

Same AC network, but with applydcopf!. Set a negative series susceptance ($B\approx-1/X$ for inductive lines) on every AC line, then call applydcopf! once the whole network is built and before optimisation.

using Posy2
using Nosy
using HiGHS
import JuMP: set_silent

# Simulation and Posy2 input configuration
sim = Sim(Model(HiGHS.Optimizer); mesh=TimeMesh())
set_silent(model(sim))
example_data_dir = joinpath(pkgdir(Posy2), "data")
snapshot = Snapshot(sim, Dict(:posy => Posy2Options(
    data_dir=example_data_dir,
    techdata_file="tech_data.xlsx",
    timeseries_file="time_series.xlsx",
    tech_mode=:excel,
    timeseries_mode=:arguments,
)))

# Electricity zones and CO2 sink
a = Node("A", EnergyCarrier("electricity A", sim), rule=:curtailed, evalprice=true, tags=[:electricity])
b = Node("B", EnergyCarrier("electricity B", sim), rule=:curtailed, evalprice=true, tags=[:electricity])
c = Node("C", EnergyCarrier("electricity C", sim), rule=:curtailed, evalprice=true, tags=[:electricity])
d = Node("D", EnergyCarrier("electricity D", sim), rule=:curtailed, evalprice=true, tags=[:electricity])
co2 = Node("CO2", CO2Carrier("CO2", sim), rule=:curtailed, tags=[:co2])

# Flat 1 MW demand in each zone
makedemand("Demand", "A", a, snapshot; profile_multiplier=0.0, annual_flat_demand=1.0 * 8760)
makedemand("Demand", "B", b, snapshot; profile_multiplier=0.0, annual_flat_demand=1.0 * 8760)
makedemand("Demand", "C", c, snapshot; profile_multiplier=0.0, annual_flat_demand=1.0 * 8760)
makedemand("Demand", "D", d, snapshot; profile_multiplier=0.0, annual_flat_demand=1.0 * 8760)

# 10 MW CCGT on A; costs from the technology workbook
makedispatchable("CCGT", a, snapshot; co2_node=co2, tech_column="CCGT", cap=10.0, unit_size=0.0)

# AC ring with susceptances
maketransmissionlink("IC", a, b, snapshot; cap=10.0, susceptance=-1.0)
maketransmissionlink("IC", b, c, snapshot; cap=10.0, susceptance=-2.0)
maketransmissionlink("IC", c, d, snapshot; cap=10.0, susceptance=-1.5)
maketransmissionlink("IC", d, a, snapshot; cap=10.0, susceptance=-2.5)
# AC diagonal A–C with susceptance
maketransmissionlink("IC", a, c, snapshot; cap=10.0, susceptance=-3.0)

# Apply KVL, then optimise
applydcopf!(snapshot)
optimize!(snapshot, cost(snapshot))
result = extract(snapshot)
julia> write_results(result, "results/dc-opf.xlsx", overwrite=true)
"results/dc-opf.xlsx"

That writes results/dc-opf.xlsx. The Interconnection volume table in Annual values (all) matches the corridor volumes below (TWh/y):

From \ ToABCDTotal
A0.0060.0120.0090.026
B000
C00.00300.003
D000
Total00.0090.0120.0090.03

Compared with the transport workbook: plants and demand are the same; what changes is the network model, and the corridor volumes shift once KVL and the line susceptances constrain the path through the mesh.

Controllable DC lines

maketransmissionlink tags each line as AC (dc=false, the default) or DC (dc=true). Only AC lines enter the susceptance matrix and the cycle basis used by applydcopf!.

That exclusion matches how HVDC behaves in a real grid. An AC corridor’s flow is tied to the voltage-angle differences around meshed lines, so it must satisfy KVL on every independent AC cycle. A controllable DC corridor (for example an HVDC line with converter stations) is different: operators set or schedule the transfer directly, and the line does not follow an AC phase-angle loop law. Posy2 therefore keeps DC lines in the energy balance and capacity limits, but outside the AC susceptance graph.

The example below replaces the AC diagonal with HVDC (dc=true).

using Posy2
using Nosy
using HiGHS
import JuMP: set_silent

# Simulation and Posy2 input configuration
sim = Sim(Model(HiGHS.Optimizer); mesh=TimeMesh())
set_silent(model(sim))
example_data_dir = joinpath(pkgdir(Posy2), "data")
snapshot = Snapshot(sim, Dict(:posy => Posy2Options(
    data_dir=example_data_dir,
    techdata_file="tech_data.xlsx",
    timeseries_file="time_series.xlsx",
    tech_mode=:excel,
    timeseries_mode=:arguments,
)))

# Electricity zones and CO2 sink
a = Node("A", EnergyCarrier("electricity A", sim), rule=:curtailed, evalprice=true, tags=[:electricity])
b = Node("B", EnergyCarrier("electricity B", sim), rule=:curtailed, evalprice=true, tags=[:electricity])
c = Node("C", EnergyCarrier("electricity C", sim), rule=:curtailed, evalprice=true, tags=[:electricity])
d = Node("D", EnergyCarrier("electricity D", sim), rule=:curtailed, evalprice=true, tags=[:electricity])
co2 = Node("CO2", CO2Carrier("CO2", sim), rule=:curtailed, tags=[:co2])

# Flat 1 MW demand in each zone
makedemand("Demand", "A", a, snapshot; profile_multiplier=0.0, annual_flat_demand=1.0 * 8760)
makedemand("Demand", "B", b, snapshot; profile_multiplier=0.0, annual_flat_demand=1.0 * 8760)
makedemand("Demand", "C", c, snapshot; profile_multiplier=0.0, annual_flat_demand=1.0 * 8760)
makedemand("Demand", "D", d, snapshot; profile_multiplier=0.0, annual_flat_demand=1.0 * 8760)

# 10 MW CCGT on A; costs from the technology workbook
makedispatchable("CCGT", a, snapshot; co2_node=co2, tech_column="CCGT", cap=10.0, unit_size=0.0)

# AC ring with susceptances
maketransmissionlink("IC", a, b, snapshot; cap=10.0, susceptance=-1.0)
maketransmissionlink("IC", b, c, snapshot; cap=10.0, susceptance=-2.0)
maketransmissionlink("IC", c, d, snapshot; cap=10.0, susceptance=-1.5)
maketransmissionlink("IC", d, a, snapshot; cap=10.0, susceptance=-2.5)
# HVDC diagonal A–C (outside KVL)
maketransmissionlink("HVDC", a, c, snapshot; cap=10.0, dc=true)

# Apply KVL, then optimise
applydcopf!(snapshot)
optimize!(snapshot, cost(snapshot))
result_dc = extract(snapshot)
julia> write_results(result_dc, "results/dc-opf-hvdc.xlsx", overwrite=true)
"results/dc-opf-hvdc.xlsx"

That creates results/dc-opf-hvdc.xlsx. With the HVDC diagonal, the AC and DC volume blocks in Annual values (all) disagree on purpose (TWh/y):

Interconnection volume (AC)

From \ ToABCDTotal
A0.0030.0040.007
B000
C0.0070.0040.011
D000
Total00.00900.0090.018

Interconnection volume (DC)

From \ ToABCDTotal
A0.020.02
B0
C00
D0
Total000.0200.02