AC Optimal Power Flow
In this example, we perform multiple AC optimal power flow analyses using the power system shown in Figure 1. The scenarios represent quasi-steady-state operation with changing constraints and topology.
Figure 1: The 4-bus power system.
Users can download a Julia script containing the scenarios from this section using the following link.
We begin by defining the units for active and reactive powers, voltage magnitudes, and voltage angles used throughout this example:
@power(MW, MVAr)
@voltage(pu, deg)Next, we define bus parameters for the AC optimal power flow analysis, including the slack bus (type = 3), active and reactive power loads, and shunt capacitor banks with conductance and susceptance values. We set voltage magnitude limits using minMagnitude and maxMagnitude. With these definitions, we begin building the power system model:
system = powerSystem()
@bus(minMagnitude = 0.95, maxMagnitude = 1.05)
addBus!(system; label = "Bus 1", type = 3)
addBus!(system; label = "Bus 2", active = 20.2, reactive = 10.5)
addBus!(system; label = "Bus 3", conductance = 0.1, susceptance = 8.2)
addBus!(system; label = "Bus 4", active = 50.8, reactive = 23.1)Next, we define branch resistance, reactance, and susceptance values. We leave branch flow constraints unset for now and introduce them later in the example:
@branch(label = "Branch ?", reactance = 0.22)
addBranch!(system; from = "Bus 1", to = "Bus 3", resistance = 0.02, susceptance = 0.05)
addBranch!(system; from = "Bus 1", to = "Bus 2", resistance = 0.05, susceptance = 0.04)
addBranch!(system; from = "Bus 2", to = "Bus 3", resistance = 0.04, susceptance = 0.04)
addBranch!(system; from = "Bus 3", to = "Bus 4", turnsRatio = 0.98)We define the active and reactive power outputs of the generators, which serve as initial primal values for the generator output variables. Reactive outputs are limited by minReactive and maxReactive, while active outputs vary between minActive and maxActive:
@generator(label = "Generator ?", minActive = 2.0, minReactive = -15.5, maxReactive = 15.5)
addGenerator!(system; bus = "Bus 1", active = 63.1, reactive = 8.2, maxActive = 65.5)
addGenerator!(system; bus = "Bus 2", active = 3.0, reactive = 6.2, maxActive = 20.5)
addGenerator!(system; bus = "Bus 2", active = 4.1, reactive = 8.5, maxActive = 22.4)Finally, we define active power generation costs in polynomial form by setting active = 2. We then specify quadratic cost functions using the polynomial keyword:
cost!(system; generator = "Generator 1", active = 2, polynomial = [0.04; 20.0; 0.0])
cost!(system; generator = "Generator 2", active = 2, polynomial = [1.00; 20.0; 0.0])
cost!(system; generator = "Generator 3", active = 2, polynomial = [1.00; 20.0; 0.0])After defining the power system data, we generate an AC model with the vectors and matrices required for analysis, including the nodal admittance matrix:
acModel!(system)Display Data Settings
Before running simulations, we configure which data elements to display and the numeric format for power values.
For bus-related data, we set:
show1 = Dict("Power Injection" => false)
fmt1 = Dict("Power Generation" => "%.2f", "Power Demand" => "%.2f", "Shunt Power" => "%.2f")Similarly, for branch-related data, we choose:
show2 = Dict("Shunt Power" => false, "Status" => false)
fmt2 = Dict("From-Bus Power" => "%.2f", "To-Bus Power" => "%.2f", "Series Power" => "%.2f")For generator-related data, we also set:
show3 = Dict("Reactive Power Capability" => false)
fmt3 = Dict("Power Output" => "%.2f")Base Case Analysis
First, we create the AC optimal power flow model with the Ipopt solver. We then solve the model to determine bus voltage magnitudes and angles and generator active and reactive power outputs. Finally, we compute the remaining bus and branch power values:
analysis = acOptimalPowerFlow(system, Ipopt.Optimizer)
powerFlow!(analysis, power = true, verbose = 1)EXIT: The optimal solution was found.Once the AC optimal power flow is solved, we inspect the bus results, including the optimal voltage magnitudes and angles:
printBusData(analysis; show = show1, fmt = fmt1)|------------------------------------------------------------------------------------------|
| Bus Data |
|------------------------------------------------------------------------------------------|
| Label | Voltage | Power Generation | Power Demand | Shunt Power |
| | | | | |
| Bus | Magnitude | Angle | Active | Reactive | Active | Reactive | Active | Reactive |
| | [pu] | [deg] | [MW] | [MVAr] | [MW] | [MVAr] | [MW] | [MVAr] |
|-------|-----------|----------|--------|----------|--------|----------|--------|----------|
| Bus 1 | 1.0500 | 0.0000 | 65.50 | 7.96 | 0.00 | 0.00 | 0.00 | -0.00 |
| Bus 2 | 1.0391 | -3.0998 | 6.31 | 15.52 | 20.20 | 10.50 | 0.00 | -0.00 |
| Bus 3 | 1.0182 | -4.4294 | 0.00 | 0.00 | 0.00 | 0.00 | 0.10 | -8.50 |
| Bus 4 | 0.9809 | -10.7249 | 0.00 | 0.00 | 50.80 | 23.10 | 0.00 | -0.00 |
|------------------------------------------------------------------------------------------|The optimal generator active and reactive power outputs are:
printGeneratorData(analysis; fmt = fmt3)|--------------------------------------------------|
| Generator Data |
|--------------------------------------------------|
| Label | Power Output | Status |
| | | |
| Generator | Bus | Active | Reactive | |
| | | [MW] | [MVAr] | |
|-------------|-------|--------|----------|--------|
| Generator 1 | Bus 1 | 65.50 | 7.96 | 1 |
| Generator 2 | Bus 2 | 3.16 | 7.74 | 1 |
| Generator 3 | Bus 2 | 3.16 | 7.79 | 1 |
|--------------------------------------------------|The generator data show that Generator 1, which has the lowest cost, produces power at its maximum output. Since Generator 2 and Generator 3 have identical cost functions, they produce equal active power.
Users can also display bus, branch, or generator constraints from the optimal power flow analysis. For example, the nonzero dual variables for Generator 1 indicate that its output has reached the limit:
printGeneratorConstraint(analysis; show = show3)|--------------------------------------------------------|
| Generator Constraint Data |
|--------------------------------------------------------|
| Label | Active Power Capability |
| | |
| | Minimum | Solution | Maximum | Dual |
| | [MW] | [MW] | [MW] | [$/MW-hr] |
|-------------|---------|----------|---------|-----------|
| Generator 1 | 2.0000 | 65.5000 | 65.5000 | -0.5987 |
| Generator 2 | 2.0000 | 3.1563 | 20.5000 | 0.0000 |
| Generator 3 | 2.0000 | 3.1563 | 22.4000 | 0.0000 |
|--------------------------------------------------------|Finally, we inspect the branch results:
printBranchData(analysis; show = show2, fmt = fmt2)|------------------------------------------------------------------------------------------|
| Branch Data |
|------------------------------------------------------------------------------------------|
| Label | From-Bus Power | To-Bus Power | Series Power |
| | | | |
| Branch | From-Bus | To-Bus | Active | Reactive | Active | Reactive | Active | Reactive |
| | | | [MW] | [MVAr] | [MW] | [MVAr] | [MW] | [MVAr] |
|----------|----------|--------|--------|----------|--------|----------|--------|----------|
| Branch 1 | Bus 1 | Bus 3 | 38.72 | 10.34 | -38.42 | -12.36 | 0.30 | 3.33 |
| Branch 2 | Bus 1 | Bus 2 | 26.78 | -2.39 | -26.45 | -0.55 | 0.33 | 1.43 |
| Branch 3 | Bus 2 | Bus 3 | 12.57 | 5.57 | -12.48 | -9.36 | 0.08 | 0.44 |
| Branch 4 | Bus 3 | Bus 4 | 50.80 | 30.22 | -50.80 | -23.10 | 0.00 | 7.12 |
|------------------------------------------------------------------------------------------|The resulting active and reactive power flows are shown in Figure 2.
(a) Active powers.
(b) Reactive powers.
Figure 2: Power flows in the 4-bus power system for the base case scenario.
Modifying Demands
Next, we update the active and reactive power demands. These changes modify both the power system model and the AC optimal power flow model:
updateBus!(analysis; label = "Bus 2", active = 25.2, reactive = 13.5)
updateBus!(analysis; label = "Bus 4", active = 43.3, reactive = 18.6)We then solve the AC optimal power flow again without recreating the model. This enables a warm start because the initial primal and dual values come from the base case:
powerFlow!(analysis, power = true, verbose = 1)EXIT: The optimal solution was found.We can now inspect the generator power outputs:
printGeneratorData(analysis; fmt = fmt3)|--------------------------------------------------|
| Generator Data |
|--------------------------------------------------|
| Label | Power Output | Status |
| | | |
| Generator | Bus | Active | Reactive | |
| | | [MW] | [MVAr] | |
|-------------|-------|--------|----------|--------|
| Generator 1 | Bus 1 | 63.65 | 3.37 | 1 |
| Generator 2 | Bus 2 | 2.79 | 7.66 | 1 |
| Generator 3 | Bus 2 | 2.79 | 7.66 | 1 |
|--------------------------------------------------|Compared with the base case, all generators reduce their power output because demand is lower. Although Generator 1 has the lowest cost, it no longer operates at maximum output because the optimal power flow must also satisfy power balance and bus voltage magnitude constraints.
We then inspect the branch results for additional insight into power flows:
printBranchData(analysis; show = show2, fmt = fmt2)|------------------------------------------------------------------------------------------|
| Branch Data |
|------------------------------------------------------------------------------------------|
| Label | From-Bus Power | To-Bus Power | Series Power |
| | | | |
| Branch | From-Bus | To-Bus | Active | Reactive | Active | Reactive | Active | Reactive |
| | | | [MW] | [MVAr] | [MW] | [MVAr] | [MW] | [MVAr] |
|----------|----------|--------|--------|----------|--------|----------|--------|----------|
| Branch 1 | Bus 1 | Bus 3 | 35.73 | 5.99 | -35.49 | -8.69 | 0.25 | 2.70 |
| Branch 2 | Bus 1 | Bus 2 | 27.91 | -2.62 | -27.56 | -0.19 | 0.35 | 1.56 |
| Branch 3 | Bus 2 | Bus 3 | 7.95 | 2.00 | -7.92 | -6.11 | 0.03 | 0.16 |
| Branch 4 | Bus 3 | Bus 4 | 43.30 | 23.45 | -43.30 | -18.60 | -0.00 | 4.85 |
|------------------------------------------------------------------------------------------|The resulting active and reactive power flows are shown in Figure 3.
(a) Active powers.
(b) Reactive powers.
Figure 3: Power flows in the 4-bus power system with modified demands.
Modifying Generator Costs
We modify the cost functions for all generators, which changes the objective function of the AC optimal power flow. The updated cost function shifts Generator 1 from the lowest-cost to the highest-cost generator in the system. Updating both models enables a warm start for the optimization problem:
cost!(analysis; generator = "Generator 1", active = 2, polynomial = [2.0; 20.0; 0.0])
cost!(analysis; generator = "Generator 2", active = 2, polynomial = [0.8; 20.0; 0.0])
cost!(analysis; generator = "Generator 3", active = 2, polynomial = [0.8; 20.0; 0.0])Next, we solve the updated problem and compute the resulting powers:
powerFlow!(analysis, power = true, verbose = 1)EXIT: The optimal solution was found.The optimal generator active and reactive power outputs are:
printGeneratorData(analysis; fmt = fmt3)|--------------------------------------------------|
| Generator Data |
|--------------------------------------------------|
| Label | Power Output | Status |
| | | |
| Generator | Bus | Active | Reactive | |
| | | [MW] | [MVAr] | |
|-------------|-------|--------|----------|--------|
| Generator 1 | Bus 1 | 25.98 | 1.87 | 1 |
| Generator 2 | Bus 2 | 20.50 | 7.11 | 1 |
| Generator 3 | Bus 2 | 22.40 | 7.11 | 1 |
|--------------------------------------------------|In this scenario, the increased cost of Generator 1 causes Generator 2 and Generator 3 to increase production to their maximum outputs. Generator 1 then supplies the remaining active power.
We can also inspect the branch results for this scenario:
printBranchData(analysis; show = show2, fmt = fmt2)|------------------------------------------------------------------------------------------|
| Branch Data |
|------------------------------------------------------------------------------------------|
| Label | From-Bus Power | To-Bus Power | Series Power |
| | | | |
| Branch | From-Bus | To-Bus | Active | Reactive | Active | Reactive | Active | Reactive |
| | | | [MW] | [MVAr] | [MW] | [MVAr] | [MW] | [MVAr] |
|----------|----------|--------|--------|----------|--------|----------|--------|----------|
| Branch 1 | Bus 1 | Bus 3 | 23.14 | 4.30 | -23.03 | -8.55 | 0.11 | 1.17 |
| Branch 2 | Bus 1 | Bus 2 | 2.84 | -2.43 | -2.83 | -1.96 | 0.00 | 0.02 |
| Branch 3 | Bus 2 | Bus 3 | 20.53 | 2.69 | -20.37 | -6.13 | 0.16 | 0.89 |
| Branch 4 | Bus 3 | Bus 4 | 43.30 | 23.41 | -43.30 | -18.60 | 0.00 | 4.81 |
|------------------------------------------------------------------------------------------|Figure 4 shows the power flows for this scenario. Compared with the previous scenario, Branch 2 has significantly lower active power flow, while Branch 3 becomes more heavily loaded.
(a) Active powers.
(b) Reactive powers.
Figure 4: Power flows in the 4-bus power system with modified generator costs.
Adding Branch Flow Constraints
To limit active power flow, we add constraints to Branch 2 and Branch 3 by setting type = 1 and specifying the from-bus limit with maxFromBus:
updateBranch!(analysis; label = "Branch 2", type = 1, maxFromBus = 15.0)
updateBranch!(analysis; label = "Branch 3", type = 1, maxFromBus = 15.0)Next, we solve the updated AC optimal power flow:
powerFlow!(analysis, power = true, verbose = 1)EXIT: The optimal solution was found.We can now inspect the generator outputs:
printGeneratorData(analysis; fmt = fmt3)|--------------------------------------------------|
| Generator Data |
|--------------------------------------------------|
| Label | Power Output | Status |
| | | |
| Generator | Bus | Active | Reactive | |
| | | [MW] | [MVAr] | |
|-------------|-------|--------|----------|--------|
| Generator 1 | Bus 1 | 42.10 | -13.52 | 1 |
| Generator 2 | Bus 2 | 13.46 | 15.50 | 1 |
| Generator 3 | Bus 2 | 13.46 | 15.50 | 1 |
|--------------------------------------------------|The power flow limit on Branch 3 forces Generator 1 to increase its active power output despite its higher cost than Generator 2 and Generator 3. The solution also shows a significant redistribution of reactive power production.
The branch constraints show that active power at the from-bus end of Branch 3 reaches the defined limit, causing the redistribution described above. The power flow on Branch 2 remains within its specified limit:
printBranchConstraint(analysis)|-----------------------------------------------------|
| Branch Constraint Data |
|-----------------------------------------------------|
| Label | From-Bus Active Power Flow |
| | |
| | Minimum | Solution | Maximum | Dual |
| | [MW] | [MW] | [MW] | [$/MW-hr] |
|----------|---------|----------|---------|-----------|
| Branch 2 | 0.0000 | 13.4196 | 15.0000 | -0.0000 |
| Branch 3 | 0.0000 | 15.0000 | 15.0000 | -439.7438 |
|-----------------------------------------------------|Finally, we inspect the branch data to examine the power redistribution in detail:
printBranchData(analysis; show = show2, fmt = fmt2)|------------------------------------------------------------------------------------------|
| Branch Data |
|------------------------------------------------------------------------------------------|
| Label | From-Bus Power | To-Bus Power | Series Power |
| | | | |
| Branch | From-Bus | To-Bus | Active | Reactive | Active | Reactive | Active | Reactive |
| | | | [MW] | [MVAr] | [MW] | [MVAr] | [MW] | [MVAr] |
|----------|----------|--------|--------|----------|--------|----------|--------|----------|
| Branch 1 | Bus 1 | Bus 3 | 28.68 | -0.26 | -28.52 | -3.31 | 0.16 | 1.71 |
| Branch 2 | Bus 1 | Bus 2 | 13.42 | -13.25 | -13.28 | 9.55 | 0.14 | 0.63 |
| Branch 3 | Bus 2 | Bus 3 | 15.00 | 7.95 | -14.88 | -11.60 | 0.12 | 0.65 |
| Branch 4 | Bus 3 | Bus 4 | 43.30 | 23.50 | -43.30 | -18.60 | 0.00 | 4.90 |
|------------------------------------------------------------------------------------------|The resulting power flows are shown in Figure 5.
(a) Active powers.
(b) Reactive powers.
Figure 5: Power flows in the 4-bus power system with added branch flow constraints.
Modifying Network Topology
Finally, we set Branch 2 out-of-service:
updateBranch!(analysis; label = "Branch 2", status = 0)We then solve the updated AC optimal power flow:
powerFlow!(analysis; power = true, verbose = 1)EXIT: The optimal solution was found.We can now inspect the updated generator outputs:
printGeneratorData(analysis; fmt = fmt3)|--------------------------------------------------|
| Generator Data |
|--------------------------------------------------|
| Label | Power Output | Status |
| | | |
| Generator | Bus | Active | Reactive | |
| | | [MW] | [MVAr] | |
|-------------|-------|--------|----------|--------|
| Generator 1 | Bus 1 | 28.66 | 4.88 | 1 |
| Generator 2 | Bus 2 | 20.10 | 7.94 | 1 |
| Generator 3 | Bus 2 | 20.10 | 7.94 | 1 |
|--------------------------------------------------|Because Branch 2 is out-of-service and Branch 3 is flow-limited, Generator 1 has less ability to supply the load at Bus 2, so its output decreases. As a result, Generator 2 and Generator 3 increase their output.
The branch data show that active power flows in the remaining in-service branches remain largely unchanged. After the outage of Branch 2, Generator 2 and Generator 3 supply the load at Bus 2, effectively displacing Generator 1:
printBranchData(analysis; show = show2, fmt = fmt2)|------------------------------------------------------------------------------------------|
| Branch Data |
|------------------------------------------------------------------------------------------|
| Label | From-Bus Power | To-Bus Power | Series Power |
| | | | |
| Branch | From-Bus | To-Bus | Active | Reactive | Active | Reactive | Active | Reactive |
| | | | [MW] | [MVAr] | [MW] | [MVAr] | [MW] | [MVAr] |
|----------|----------|--------|--------|----------|--------|----------|--------|----------|
| Branch 1 | Bus 1 | Bus 3 | 28.66 | 4.88 | -28.50 | -8.54 | 0.16 | 1.76 |
| Branch 2 | Bus 1 | Bus 2 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| Branch 3 | Bus 2 | Bus 3 | 15.00 | 2.37 | -14.91 | -6.18 | 0.09 | 0.50 |
| Branch 4 | Bus 3 | Bus 4 | 43.30 | 23.42 | -43.30 | -18.60 | 0.00 | 4.82 |
|------------------------------------------------------------------------------------------|Figure 6 shows the resulting power flows with Branch 2 out-of-service.
(a) Active powers.
(b) Reactive powers.
Figure 6: Power flows in the 4-bus power system with modified network topology.