DC Power Flow

To perform the DC power flow, we first need to have the PowerSystem type that has been created with the DC model. Following that, we can construct the power flow model encapsulated within the DCPowerFlow type by employing the following function:

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


After obtaining the solution for DC power flow, JuliaGrid offers a post-processing analysis function to compute active powers associated with buses, branches, and generators:

Additionally, specialized functions are available for calculating specific types of powers for individual buses, branches, or generators.


Bus Type Modification

During the initialization process, the designated slack bus, which is initially set, undergoes examination and can be altered using the dcPowerFlow function. Here is an example:

system = powerSystem()

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

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

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

dcModel!(system)
analysis = dcPowerFlow(system)

In this example, the slack bus (type = 3) corresponds to the Bus 1. However, this bus does not have an in-service generator connected to it. JuliaGrid considers this a mistake and attempts to assign a new slack bus from the available generator buses (type = 2) that have connected in-service generators. In this particular example, the Bus 3 will become the new slack bus. As a result, we can observe the updated array of bus types:

julia> print(system.bus.label, system.bus.layout.type)Bus 1: 1
Bus 2: 2
Bus 3: 3
Info

The bus that is defined as the slack bus (type = 3) but lacks a connected in-service generator will have its type changed to the demand bus (type = 1). Meanwhile, the first generator bus (type = 2) with an in-service generator connected to it will be assigned as the new slack bus (type = 3).


Power Flow Solution

To solve the DC power flow problem using JuliaGrid, we 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

Here, the user triggers LU factorization as the default method for solving the DC power flow problem. However, the user also has the option to select alternative factorization methods such as LDLt or QR, for instance:

analysis = dcPowerFlow(system, LDLt)

To obtain the bus voltage angles, we can call the solve! function as follows:

solve!(system, 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 insights, we suggest referring to the tutorial on DC Power Flow Analysis.


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

@voltage(pu, deg, V)
printBusData(system, analysis)
|-----------------|
| Bus Data        |
|-----------------|
| Label | Voltage |
|       |         |
|   Bus |   Angle |
|       |   [deg] |
|-------|---------|
| Bus 1 |  0.0000 |
| Bus 2 | -0.1023 |
| Bus 3 | -0.0655 |
|-----------------|

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

printBusData(system, analysis; label = "Bus 1", header = true)
printBusData(system, analysis; label = "Bus 2")
printBusData(system, 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 as follows:

open("bus.txt", "w") do file
    printBusData(system, 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(system, analysis, io; style = false)
CSV.write("bus.csv", CSV.File(take!(io); delim = "|"))

Power System Update

After establishing the PowerSystem type using the powerSystem function and configuring the DC model with dcModel!, users gain the capability to incorporate new branches and generators. Furthermore, they can adjust buses, branches, and generators.

Once updates are completed, users can progress towards generating the DCPowerFlow type using the dcPowerFlow function. Ultimately, resolving the DC power flow is achieved through the utilization of the solve! function:

system = powerSystem() # <- Initialize the PowerSystem instance

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.05)

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

dcModel!(system)
analysis = dcPowerFlow(system) # <- Build DCPowerFlow for the defined power system
solve!(system, analysis)

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

addBranch!(system; label = "Branch 2", from = "Bus 1", to = "Bus 2", reactance = 1)
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) # <- Build DCPowerFlow for the updated power system
solve!(system, 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

An advanced methodology involves users establishing the DCPowerFlow type using dcPowerFlow just once. After this initial setup, users can integrate new branches and generators, and also have the capability to modify buses, branches, and generators, all without the need to recreate the DCPowerFlow type.

This advancement extends beyond the previous scenario where recreating the PowerSystem and DC model was unnecessary, to now include the scenario where DCPowerFlow also does not need to be recreated. Such efficiency can be particularly advantageous in cases where JuliaGrid can reuse nodal matrix factorization.

By modifying the previous example, we observe that we now create the DCPowerFlow type only once:

system = powerSystem() # <- Initialize the PowerSystem instance

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.05)

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

dcModel!(system)
analysis = dcPowerFlow(system) # <- Build DCPowerFlow for the defined power system
solve!(system, analysis)

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

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

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

# <- No need for re-build; we have already updated the existing DCPowerFlow instance
solve!(system, 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 LU or LDLt, as long as the nonzero pattern of the nodal matrix remains consistent between power system configurations.


Reusing Matrix Factorization

Drawing from the preceding example, our focus now shifts to finding a solution involving modifications that entail adjusting the active power demand at Bus 2, introducing a new generator at Bus 2, and fine-tuning the output power of Generator 1. It is important to note that these adjustments do not impact the branches, leaving the nodal matrix unchanged. To resolve this updated system, users can simply execute the solve! function:

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

solve!(system, analysis)
Info

In this scenario, JuliaGrid will recognize instances where the user has not modified branch parameters affecting the nodal matrix. Consequently, JuliaGrid will leverage the previously performed nodal matrix factorization, resulting in a significantly faster solution compared to recomputing the factorization.


Limitations

The dcPowerFlow function oversees bus type validations, as detailed in the Bus Type Modification section. Consequently, if a user intends to change the slack bus or leaves an existing slack bus without a generator, proceeding directly to the solve! function is not feasible.

In these instances, JuliaGrid will raise an error:

julia> updateGenerator!(system, analysis; label = "Generator 1", status = 0)ERROR: The power flow model cannot be reused due to required bus type conversion.

Now, the user must execute the dcPowerFlow function instead of attempting to reuse the DCPowerFlow type:

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

analysis = dcPowerFlow(system)
solve!(system, analysis)
Info

After creating the PowerSystem and DCPowerFlow types, users can add or modify buses, branches, and generators before directly using the solve! function. JuliaGrid automatically executes the necessary functions when adjustments lead to a valid solution. However, if modifications are incompatible, like changing the slack bus, JuliaGrid raises an error to prevent misleading outcomes, ensuring accuracy.


Power Analysis

After obtaining the solution, we can calculate powers related to buses, branches, and generators using the power! function. For example, let us consider the power system for which we obtained 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)
solve!(system, analysis)

Now we can calculate the active powers using the following function:

power!(system, analysis)

Next, let us 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, we suggest referring to the tutorials on DC Power Flow Analysis.


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

@power(MW, pu, pu)
printBranchData(system, 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 active power injection associated with a specific bus, the function can be used:

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

Active Power Injection from Generators

To calculate active power injection from the generators at a specific bus, the function can be used:

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

Active Power Flow

Similarly, we can compute the active power flow at both the from-bus and to-bus ends of the specific branch by utilizing the provided functions below:

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

Generator Active Power Output

Finally, we can compute the active power output of a particular generator using the function:

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