Skip to content

DC Optimal Power Flow

Definition

Optimal Power Flow determines the optimal generator dispatch that meets system demand at minimum cost, while satisfying network constraints such as line limits and power flow equations.

DC Optimal Power Flow (DC OPF) is a linearized approximation of AC OPF that simplifies the power flow equations by assuming flat voltage magnitudes, small angle differences, no reactive power and no transmission losses. It is widely used in electricity markets to determine efficient dispatch and compute locational marginal prices (LMPs) under transmission constraints.

Mathematical formulation 🧮

Equation explanation

(1a): Objective function
This function minimizes the total generation cost over all generators in set.

(1b): Volatge Angle Difference Limit Constraint
This constraint limits the volatge angle difference across each line to ensure system stability.
It ensures that the difference in voltage angles across each transmission line stays within acceptable bounds.

(1c): Generation Limit Constraint
This constraint restricts generator output within its physical minimum and maximum limits.

(1d) & (1e): Power Flow Equations (Linearized DC)
These expressions define line flows using the DC power flow approximation, where power flow is proportional to the angle difference of the two nodes divided by the line’s reactance.

(1f) & (1g): Line Flow Limit Constraints
These constraints enforces thermal or operational limits on line power flows in both directions.

(1h): Generation-load Balance Contraint
This constraint ensures nodal active power balance at each bus.

Julia code 🔢

# Data for the problem
busset = 1:3
genset = 1:2
lineset = 1:3
slack_bus = 1
Pd = [0.0, 0.0, 150.0]
# Generator data: (bus, Pmin, Pmax, cost)
gen_data = [
(1, 0.0, 100.0, 10.0),
(2, 0.0, 100.0, 20.0)
]
# Line data: (from, to, reactance, flow_limit)
line_data = [
(1, 2, 0.1, 100.0),
(2, 3, 0.1, 100.0),
(3, 1, 0.1, 100.0)
]
# Optimization model
using JuMP, Ipopt
OPF = Model(Ipopt.Optimizer)
@variable(OPF, pg[g in genset])
@variable(OPF, θ[b in busset])
@variable(OPF, p_ft[l in lineset])
@constraint(OPF, θ[slack_bus] == 0)
@constraint(OPF, [g in genset],
gen_data[g][2] <= pg[g] <= gen_data[g][3])
@constraint(OPF, [l in lineset],
p_ft[l] == (1 / line_data[l][3]) * (θ[line_data[l][1]] - θ[line_data[l][2]]))
@constraint(OPF, [l in lineset],
-line_data[l][4] <= p_ft[l] <= line_data[l][4])
@constraint(OPF, [b in busset],
sum(pg[g] for g in genset if gen_data[g][1] == b)
+ sum(p_ft[l] for l in lineset if line_data[l][2] == b)
- sum(p_ft[l] for l in lineset if line_data[l][1] == b)
== Pd[b]
)
@objective(OPF, Min, sum(gen_data[g][4] * pg[g] for g in genset))
optimize!(OPF)

Visualization 📊

Below is a visualization that shows how generation is dispatched by network and cost as demand at bus 3 changes.

3-Bus DC-OPF Chart

Demand at Bus 3: 150 MW

Network Flows

Bus 1Bus 2Bus 3L1 1→2: 16.7 MWL2 2→3: 66.7 MWL3 3→1: -83.3 MW

Dispatch & Angles

Gen 1 @ Bus 1100 MW
Gen 2 @ Bus 250 MW
Total Cost$2000.00/h

θ₁ (slack)0.000
θ₂-1.6667 rad
θ₃-8.3333 rad