DC Power Flow

Performing DC power flow first requires the PowerSystem type that has been created with the DC model. The power flow model encapsulated within the DcPowerFlow type can then be constructed using the following function:


To solve the DC power flow problem and acquire bus voltage angles, users can use the following function:

Once the DC power flow solution is obtained, JuliaGrid provides a function for computing powers:

Alternatively, instead of using functions responsible for solving power flow and computing powers, users can use the wrapper function:

Users can also access specialized functions for computing specific types of powers for individual buses, branches, or generators within the power system.


Power Flow Solution

To solve the DC power flow problem using JuliaGrid, start by creating the PowerSystem type and defining the DC model with the dcModel! function. Here is an example:

system = powerSystem()

addBus!(system; label = "Bus 1", type = 3)
addBus!(system; label = "Bus 2", type = 1, active = 0.1)
addBus!(system; label = "Bus 3", type = 1, active = 0.05)

addBranch!(system; label = "Branch 1", from = "Bus 1", to = "Bus 2", reactance = 0.05)
addBranch!(system; label = "Branch 2", from = "Bus 1", to = "Bus 3", reactance = 0.01)
addBranch!(system; label = "Branch 3", from = "Bus 2", to = "Bus 3", reactance = 0.01)

addGenerator!(system; label = "Generator 1", bus = "Bus 1", active = 3.2)

dcModel!(system)

The dcPowerFlow function can be used to establish the DC power flow problem:

analysis = dcPowerFlow(system)
Tip

By default, the user activates LU factorization to solve the system of linear equations. The available factorization methods are LL, LDLt, LU, KLU, and QR:

analysis = dcPowerFlow(system, KLU)

The KLU method, using the Gilbert-Peierls algorithm, can significantly speed up power flow computations [1].

To obtain the bus voltage angles, call solve!:

solve!(analysis)

Once the solution is obtained, the bus voltage angles can be accessed using:

julia> print(system.bus.label, analysis.voltage.angle)Bus 1: 0.0
Bus 2: -0.0017857142857142859
Bus 3: -0.001142857142857143
Info

For implementation details, see the tutorial on DC Power Flow Analysis.


Wrapper Function

JuliaGrid provides a wrapper function for DC power flow analysis and also supports the computation of powers using the powerFlow! function:

analysis = dcPowerFlow(system)
powerFlow!(analysis; verbose = 2)
Number of entries in the nodal matrix: 9
Number of state variables:             2

EXIT: The solution of the DC power flow was found.

Users can print the results in the REPL using any units that have been configured, such as:

@voltage(pu, deg)
printBusData(analysis)
|--------------------------------|
| Bus Data                       |
|--------------------------------|
| Label | Voltage | Power Demand |
|       |         |              |
|   Bus |   Angle |       Active |
|       |   [deg] |         [pu] |
|-------|---------|--------------|
| Bus 1 |  0.0000 |       0.0000 |
| Bus 2 | -0.1023 |       0.1000 |
| Bus 3 | -0.0655 |       0.0500 |
|--------------------------------|

Next, users can customize the print results for specific buses, for example:

printBusData(analysis; label = "Bus 1", header = true)
printBusData(analysis; label = "Bus 2")
printBusData(analysis; label = "Bus 3", footer = true)

Save Results to a File

Users can also redirect print output to a file. For example, data can be saved in a text file:

open("bus.txt", "w") do file
    printBusData(analysis, file)
end

Save Results to a CSV File

For CSV output, users should first generate a simple table with style = false, and then save it to a CSV file:

using CSV

io = IOBuffer()
printBusData(analysis, io; style = false)
CSV.write("bus.csv", CSV.File(take!(io); delim = "|"))

Power System Update

Begin by creating the PowerSystem type with the powerSystem function. The DC model is then configured using the dcModel! function. After that, initialize the DcPowerFlow type through the dcPowerFlow function and solve the resulting power flow problem:

system = powerSystem()

addBus!(system; label = "Bus 1", type = 3)
addBus!(system; label = "Bus 2", type = 2, active = 2.1)

addBranch!(system; label = "Branch 1", from = "Bus 1", to = "Bus 2", reactance = 0.2)

addGenerator!(system; label = "Generator 1", bus = "Bus 1", active = 3.2)

dcModel!(system)

analysis = dcPowerFlow(system)
powerFlow!(analysis)

Next, modify the existing PowerSystem type within the DC model using add and update functions. Then, create a new DcPowerFlow type based on the modified system and solve the power flow problem:

updateBus!(system; label = "Bus 2", active = 0.4)

addBranch!(system; label = "Branch 2", from = "Bus 1", to = "Bus 2", reactance = 0.3)
updateBranch!(system; label = "Branch 1", status = 0)

addGenerator!(system; label = "Generator 2", bus = "Bus 2", active = 1.5)
updateGenerator!(system; label = "Generator 1", active = 1.9)

analysis = dcPowerFlow(system)
powerFlow!(analysis)
Info

This concept removes the need to restart and recreate the PowerSystem within the dc field from the beginning when implementing changes to the existing power system.


Power Flow Update

For advanced workflows, users can create the DcPowerFlow type once. They can then integrate new branches and generators and modify buses, branches, and generators without recreating the DcPowerFlow type. This is particularly beneficial when the previously computed nodal matrix factorization can be reused.

Now revisit the defined PowerSystem and DcPowerFlow types:

system = powerSystem()

addBus!(system; label = "Bus 1", type = 3)
addBus!(system; label = "Bus 2", type = 2, active = 2.1)

addBranch!(system; label = "Branch 1", from = "Bus 1", to = "Bus 2", reactance = 0.2)

addGenerator!(system; label = "Generator 1", bus = "Bus 1", active = 3.2)

dcModel!(system)

analysis = dcPowerFlow(system)
powerFlow!(analysis)

Next, modify the existing PowerSystem within the DC model as well as the DcPowerFlow type using add and update functions. Then, immediately solve the power flow problem:

updateBus!(analysis; label = "Bus 2", active = 0.4)

addBranch!(analysis; label = "Branch 2", from = "Bus 1", to = "Bus 2", reactance = 0.3)
updateBranch!(analysis; label = "Branch 1", status = 0)

addGenerator!(analysis; label = "Generator 2", bus = "Bus 2", active = 1.5)
updateGenerator!(analysis; label = "Generator 1", active = 1.9)

powerFlow!(analysis)
Info

This concept removes the need to restart and recreate both the PowerSystem within the dc field and the DcPowerFlow from the beginning when implementing changes to the existing power system. Additionally, JuliaGrid can reuse symbolic factorizations of LL, LDLt, LU, and KLU as long as the nonzero pattern of the nodal matrix remains consistent between power system configurations.


Reusing Matrix Factorization

Continuing from the preceding example, consider modifications that adjust the active power demand at Bus 2, introduce a new generator at Bus 2, and fine-tune the output power of Generator 1. These adjustments do not impact the branches, leaving the nodal matrix unchanged. To resolve this updated system, users can execute the powerFlow! function:

updateBus!(analysis; label = "Bus 2", active = 0.2)
addGenerator!(analysis; label = "Generator 3", bus = "Bus 2", active = 0.3)
updateGenerator!(analysis; label = "Generator 1", active = 2.1)

powerFlow!(analysis)

Here, JuliaGrid recognizes when the user has not modified branch parameters affecting the nodal matrix. Consequently, JuliaGrid reuses the previously computed nodal matrix factorization, resulting in a significantly faster solution compared to recomputing the factorization.


Limitations

Attempting to change the slack bus or leaving the existing slack bus without a connected generator and then running the power flow calculation is not feasible. In such cases, JuliaGrid will raise an error:

julia> updateGenerator!(analysis; label = "Generator 1", status = 0)ERROR: KeyError: key 1 not found

To resolve this, the user must recreate the DcPowerFlow type rather than attempting to reuse the existing one:

updateGenerator!(system; label = "Generator 1", status = 0)

analysis = dcPowerFlow(system)
powerFlow!(analysis)

Power Analysis

After obtaining the solution, calculate powers related to buses, branches, and generators using the power! function. For example, consider the power system used to obtain the DC power flow solution:

system = powerSystem()

addBus!(system; label = "Bus 1", type = 3)
addBus!(system; label = "Bus 2", type = 1, active = 0.1)
addBus!(system; label = "Bus 3", type = 1, active = 0.05)

addBranch!(system; label = "Branch 1", from = "Bus 1", to = "Bus 2", reactance = 0.05)
addBranch!(system; label = "Branch 2", from = "Bus 1", to = "Bus 3", reactance = 0.01)
addBranch!(system; label = "Branch 3", from = "Bus 2", to = "Bus 3", reactance = 0.01)

addGenerator!(system; label = "Generator 1", bus = "Bus 1", active = 3.2)

analysis = dcPowerFlow(system)
powerFlow!(analysis)

Now calculate the active powers using the following function:

power!(analysis)

Next, convert the base power unit to megavolt-amperes (MVA):

@base(system, MVA, V)

Finally, here are the calculated active power values in megawatts (MW) corresponding to buses and branches:

julia> print(system.bus.label, system.base.power.value * analysis.power.injection.active)Bus 1: 15.000000000000002
Bus 2: -10.0
Bus 3: -5.0
julia> print(system.branch.label, system.base.power.value * analysis.power.from.active)Branch 1: 3.571428571428572 Branch 2: 11.428571428571429 Branch 3: -6.42857142857143
Info

To better understand the powers associated with buses, branches, and generators that are calculated by the power! function, see the tutorials on DC Power Flow Analysis.


Users can use any of the print functions outlined in the Print Power System Data or Print Power System Summary. For example, users can print the results in the REPL using any units that have been configured:

@power(MW, pu)
printBranchData(analysis)
|-----------------------------------------------------------------------|
| Branch Data                                                           |
|-----------------------------------------------------------------------|
|            Label             | From-Bus Power | To-Bus Power | Status |
|                              |                |              |        |
|   Branch | From-Bus | To-Bus |         Active |       Active |        |
|          |          |        |           [MW] |         [MW] |        |
|----------|----------|--------|----------------|--------------|--------|
| Branch 1 | Bus 1    | Bus 2  |         3.5714 |      -3.5714 |      1 |
| Branch 2 | Bus 1    | Bus 3  |        11.4286 |     -11.4286 |      1 |
| Branch 3 | Bus 2    | Bus 3  |        -6.4286 |       6.4286 |      1 |
|-----------------------------------------------------------------------|

Active Power Injection

To calculate the active power injection associated with a specific bus, use:

julia> active = injectionPower(analysis; label = "Bus 1")0.15000000000000002

Active Power Injection from Generators

To calculate the active power injection from generators at a specific bus, use:

julia> active = supplyPower(analysis; label = "Bus 1")0.15000000000000002

Active Power Flow

Similarly, to compute the active power flow at both the from-bus and to-bus ends of a specific branch, use:

julia> active = fromPower(analysis; label = "Branch 2")0.1142857142857143
julia> active = toPower(analysis; label = "Branch 2")-0.1142857142857143

Generator Active Power Output

Finally, to compute the active power output of a particular generator, use:

julia> active = generatorPower(analysis; label = "Generator 1")0.15000000000000002