Power System Model

The JuliaGrid supports the composite type PowerSystem to preserve power system data, with the following fields: bus, branch, generator, base, and model. The fields bus, branch, and generator hold data related to buses, branches, and generators, respectively. The base field stores base values for power and voltages, with the default being three-phase power measured in volt-amperes for the base power and line-to-line voltages measured in volts for base voltages. Within the model field, there are ac and dc subfields that store vectors and matrices pertinent to the power system's topology and parameters, and these are utilized in either the AC or DC framework.

The composite type PowerSystem can be created using a function:

JuliaGrid supports three modes for populating the PowerSystem type: using built-in functions, using HDF5 file format, and using Matpower case files.

It is recommended to use the HDF5 format for large-scale systems. To facilitate this, JuliaGrid has the function:

Upon creation of the PowerSystem type, users can generate vectors and matrices based on the power system topology and parameters using the following functions:


Once the PowerSystem type is created, user can add buses, branches, generators, or manage costs associated with the output powers of the generators, using the following functions:

JuliaGrid also provides macros @bus, @branch, and @generator to define templates that aid in creating buses, branches, and generators. These templates help avoid entering the same parameters repeatedly.

Moreover, it is feasible to modify the parameters of buses, branches, and generators. When these functions are executed, all relevant fields within the PowerSystem composite type will be automatically updated, encompassing the ac and dc fields as well. These functions include:

Tip

The functions addBranch!, addGenerator!, updateBus!, updateBranch!, updateGenerator!, and cost! serve a dual purpose. While their primary function is to modify the PowerSystem composite type, they are also designed to accept various analysis models like AC or DC power flow models. When feasible, these functions not only modify the PowerSystem type but also adapt the analysis model, often resulting in improved computational efficiency. Detailed instructions on utilizing this feature can be found in dedicated manuals for specific analyses.


Build Model

The powerSystem function generates the PowerSystem composite type and requires a string-formatted path to either Matpower cases or HDF5 files as input. Alternatively, the PowerSystem can be created without any initial data by initializing it as empty, allowing the user to construct the power system from scratch.


Matpower or HDF5 File

For example, to create the PowerSystem type using the Matpower case file for the IEEE 14-bus test case, which is named case14.m and located in the folder C:\matpower, the following Julia code can be used:

system = powerSystem("C:/matpower/case14.m")

In order to use the HDF5 file as input to create the PowerSystem type, it is necessary to have saved the data using the savePowerSystem function beforehand. As an example, let us say we saved the power system as case14.h5 in the directory C:\hdf5. In this case, the following Julia code can be used to construct the PowerSystem type:

system = powerSystem("C:/hdf5/case14.h5")
Tip

It is recommended to load the power system from the HDF5 file to reduce the loading time.


Model from Scratch

Alternatively, the model can be build from the scratch using built-in functions, for example:

system = powerSystem()

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

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

Internal Unit System

The PowerSystem composite type stores all electrical quantities in per-units and radians, except for the base values of power and voltages. The base power value is expressed in volt-amperes, while the base voltages are given in volts.


Change Base Unit Prefixes

As an example, if we execute the previous code snippet, we can retrieve the base power and base voltage values and units as shown below:

julia> system.base.power.value, system.base.power.unit(1.0e8, "VA")
julia> system.base.voltage.value, system.base.voltage.unit([345000.0, 345000.0], "V")

By using the @base macro, users can change the prefixes of the base units. For instance, if the user wishes to convert base power and base voltage values to megavolt-amperes (MVA) and kilovolts (kV) respectively, they can execute the following macro:

@base(system, MVA, kV)

Upon execution of the macro, the base power and voltage values and units will be modified accordingly:

julia> system.base.power.value, system.base.power.unit(100.0, "MVA")
julia> system.base.voltage.value, system.base.voltage.unit([345.0, 345.0], "kV")

Therefore, by using the @base macro to modify the prefixes of the base units, users can convert the output data from various analyses to specific units with the desired prefixes.


Save Model

Once the PowerSystem type has been created using one of the methods outlined in Build Model, the current data can be stored in the HDF5 file by using savePowerSystem function:

savePowerSystem(system; path = "C:/matpower/case14.h5", reference = "IEEE 14-bus test case")

All electrical quantities saved in the HDF5 file are in per-units and radians, except for base values for power and voltages, which are given in volt-amperes and volts. It is important to note that even if the user modifies the base units using the @base macro, the units will still be saved in the default settings.


Add Bus

We have the option to add buses to a loaded power system or to one created from scratch. As an illustration, we can initiate the PowerSystem type and then incorporate two buses by utilizing the addBus! function:

system = powerSystem()

addBus!(system; label = "Bus 1", type = 3, active = 0.1, base = 345e3)
addBus!(system; label = "Bus 2", type = 1, angle = -0.034907, base = 345e3)

In this case, we have created two buses where the active power demanded by the consumer at Bus 1 is specified in per-units, which are the same units used to store electrical quantities:

julia> system.bus.demand.active2-element Vector{Float64}:
 0.1
 0.0

It is worth noting that the base keyword is used to specify the base voltages, and its default input unit is in volts (V).

julia> system.base.voltage.value, system.base.voltage.unit([345000.0, 345000.0], "V")

Finally, we set the bus voltage angle in radians for the Bus 2 to its initial value:

julia> system.bus.voltage.angle2-element Vector{Float64}:
  0.0
 -0.034907
Info

We recommend reading the documentation for the addBus! function, where we have provided a list of all the keywords that can be used.


Customizing Input Units for Keywords

Typically, all keywords associated with electrical quantities are expected to be provided in per-units (pu) and radians (rad) by default, with the exception of base voltages, which should be specified in volts (V). However, users can choose to use different units than the default per-units and radians or modify the prefix of the base voltage unit by using macros such as the following:

@power(MW, MVAr, pu)
@voltage(pu, deg, kV)

This practical example showcases the customization approach. For keywords tied to active powers, the unit is set as megawatts (MW), while reactive powers employ megavolt-amperes reactive (MVAr). Apparent power, on the other hand, employs per-units (pu). As for keywords concerning voltage magnitude, per-units (pu) remain the choice, but voltage angle mandates degrees (deg). Lastly, the input unit for base voltage is elected to be kilovolts (kV). This unit configuration will be applied throughout subsequent function calls after the unit definitions are established.

Now we can create identical two buses as before using new system of units as follows:

system = powerSystem()

addBus!(system; label = "Bus 1", type = 3, active = 10.0, base = 345.0)
addBus!(system; label = "Bus 2", type = 1, angle = -2.0, base = 345.0)

As can be observed, electrical quantities will continue to be stored in per-units and radians format:

julia> [system.bus.demand.active system.bus.voltage.angle]2×2 Matrix{Float64}:
 0.1   0.0
 0.0  -0.0349066

The base voltage values will still be stored in volts (V) since we only changed the input unit prefix, and did not modify the internal unit prefix, as shown below:

julia> system.base.voltage.value, system.base.voltage.unit([345000.0, 345000.0], "V")

To modify the internal unit prefix, the following macro can be used:

@base(system, VA, kV)

After executing this macro, the base voltage values will be stored in kilovolts (kV):

julia> system.base.voltage.value, system.base.voltage.unit([345.0, 345.0], "kV")

Add Branch

The branch can only be added once buses are defined, and the from and to keywords must match the bus labels already defined. For example:

system = powerSystem()

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

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

Here, we created the branch from Bus 1 to Bus 2 with following parameter:

julia> system.branch.parameter.reactance1-element Vector{Float64}:
 0.12
Info

It is recommended to consult the documentation for the addBranch! function, where we have provided a list of all the keywords that can be used.


Customizing Input Units for Keywords

To use units other than per-units (pu) and radians (rad), macros can be employed to change the input units. For example, if the need arises to use ohms (Ω), the macros below can be employed:

@parameter(Ω, pu)

system = powerSystem()

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

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

Still, all electrical quantities are stored in per-units, and the same branch as before is created:

julia> system.branch.parameter.reactance1-element Vector{Float64}:
 0.11999999999999998

It is important to note that, when working with impedance and admittance values in ohms (Ω) and siemens (S) that are related to a transformer, the assignment must be based on the primary side of the transformer.


Add Generator

After defining the buses, generators can be added to the power system. Each generator must have a unique label, and the bus keyword should correspond to the unique label of the bus it is connected to. For instance:

system = powerSystem()

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

addGenerator!(system; label = "Generator 1", bus = "Bus 2", active = 0.5, reactive = 0.1)

In the above code, we add the generator to the Bus 2, with active and reactive power outputs set to:

julia> [system.generator.output.active system.generator.output.reactive]1×2 Matrix{Float64}:
 0.5  0.1

Similar to buses and branches, the input units can be changed to units other than per-units using different macros.

Info

It is recommended to refer to the documentation for the addGenerator! function, where we have provided a list of all the keywords that can be used.


Add Templates

The functions addBus!, addBranch!, and addGenerator! are used to add bus, branch, and generator to the power system, respectively. If certain keywords are not specified, default values are assigned to some parameters.


Default Keyword Values

Regarding the addBus! function, the bus type is automatically configured as a demand bus with type = 1. The initial bus voltage magnitude is set to magnitude = 1.0 per-unit, while the base voltage is established as base = 138e3 volts. Additionally, the minimum and maximum bus voltage magnitudes are set to minMagnitude = 0.9 per-unit and maxMagnitude = 1.1 per-unit, respectively.

Transitioning to the addBranch! function, the default operational status is status = 1, indicating that the branch is in-service. The off-nominal turns ratio for the transformer is specified as turnsRatio = 1.0, and the phase shift angle is set to shiftAngle = 0.0, collectively defining the line configuration with these standard settings. The flow rating is also configured as type = 1. Moreover, the minimum and maximum voltage angle differences between the from-bus and to-bus ends are set to minDiffAngle = -2pi and maxDiffAngle = 2pi, respectively.

Similarly, the addGenerator! function designates an operational generator by employing status = 1, and it sets magnitude = 1.0 per-unit, denoting the desired voltage magnitude setpoint.

The remaining parameters are initialized with default values of zero.


Change Default Keyword Values

In JuliaGrid, users have the flexibility to adjust default values and assign customized values using the @bus, @branch, and @generator macros. These macros create bus, branch, and generator templates that are used every time the addBus!, addBranch!, and addGenerator! functions are called. For instance, the code block shows an example of creating bus, branch, and generator templates with customized default values:

system = powerSystem()

@bus(type = 2, active = 0.1)
addBus!(system; label = "Bus 1")
addBus!(system; label = "Bus 2", type = 1, active = 0.5)

@branch(reactance = 0.12)
addBranch!(system; label = "Branch 1", from = "Bus 1", to = "Bus 2")
addBranch!(system; label = "Branch 2", from = "Bus 1", to = "Bus 2", reactance = 0.06)

@generator(magnitude = 1.1)
addGenerator!(system; label = "Generator 1", bus = "Bus 1", active = 0.6)
addGenerator!(system; label = "Generator 2", bus = "Bus 1", active = 0.2)

This code example involves two uses of the addBus! and addBranch! functions. In the first use, the functions rely on the default values set by the templates created with the @bus and @branch macros. In contrast, the second use passes specific values that match the keywords used in the templates. As a result, the templates are ignored:

julia> system.bus.layout.type2-element Vector{Int8}:
 2
 1
julia> [system.bus.demand.active system.branch.parameter.reactance]2×2 Matrix{Float64}: 0.1 0.12 0.5 0.06

In the given example, the @generator macro is utilized instead of repeatedly specifying the magnitude keyword in the addGenerator! function. This macro creates a generator template with a default value for magnitude, which is automatically applied every time the addGenerator! function is called. Therefore, it eliminates the requirement to set the magnitude value for each individual generator:

julia> system.generator.voltage.magnitude2-element Vector{Float64}:
 1.1
 1.1

Customizing Input Units for Keywords

The JuliaGrid requires users to specify electrical quantity-related keywords in per-units (pu) and radians (rad) by default. However, it provides macros, such as @power, that allow users to specify other units:

system = powerSystem()

@power(MW, MVAr, MVA)
@bus(active = 100, reactive = 200)
addBus!(system; label = "Bus 1")

@power(pu, pu, pu)
addBus!(system; label = "Bus 2", active = 0.5)

In this example, we create the bus template and one bus using SI power units, and then we switch to per-units and add the second bus. It is important to note that once the template is defined in any unit system, it remains valid regardless of subsequent unit system changes. The resulting power values are:

julia> [system.bus.demand.active system.bus.demand.reactive]2×2 Matrix{Float64}:
 1.0  2.0
 0.5  2.0

Thus, JuliaGrid automatically tracks the unit system used to create templates and provides the appropriate conversion to per-units and radians. Even if the user switches to a different unit system later on, the previously defined template will still be valid.


Multiple Templates

In the case of calling the @bus, @branch, or @generator macros multiple times, the provided keywords and values will be combined into a single template for the corresponding component (bus, branch, or generator), which will be used for generating the component.


Reset Templates

To reset the bus, branch, and generator templates to their default settings, users can utilize the following macros:

@default(bus)
@default(branch)
@default(generator)

Additionally, users can reset all templates using the macro:

@default(template)

Labels

As we shown above, JuliaGrid mandates a distinctive label for every bus, branch, or generator. These labels are stored in order dictionaries, functioning as pairs of strings and integers. The string signifies the exclusive label for the specific component, whereas the integer maintains an internal numbering of buses, branches, or generators.

In contrast to the simple labeling approach, JuliaGrid offers several additional methods for labeling. The choice of method depends on the specific needs and can potentially be more straightforward.


Integer-Based Labeling

If users prefer to utilize integers as labels in various functions, this is acceptable. However, it is important to note that despite using integers, these labels are still stored as strings. Let us take a look at the following illustration:

system = powerSystem()

addBus!(system; label = 1, type = 3, active = 0.1)
addBus!(system; label = 2, type = 1, angle = -0.2)

addBranch!(system; label = 1, from = 1, to = 2, reactance = 0.12)

addGenerator!(system; label = 1, bus = 2, active = 0.5, reactive = 0.1)

In this example, we create two buses labelled as 1 and 2. The branch is established between these two buses with a unique branch label of 1. Finally, the generator is connected to the bus labelled 2 and has its distinct label set to 1.


Automated Labeling

Users also possess the option to omit the label keyword, allowing JuliaGrid to independently allocate unique labels for buses, branches, or generators. In such instances, JuliaGrid employs an ordered set of incremental integers for labeling components. To illustrate, consider the subsequent example:

system = powerSystem()

addBus!(system; type = 3, active = 0.1)
addBus!(system; type = 1, angle = -0.2)

addBranch!(system; from = 1, to = 2, reactance = 0.12)

addGenerator!(system; bus = 2, active = 0.5, reactive = 0.1)

This example presents the same power system as before. In the previous example, we used an ordered set of increasing integers for labels, in line with JuliaGrid's automatic labeling behavior when the label keyword is omitted.


Automated Labeling Using Templates

Additionally, users have the ability to generate labels through templates and employ the symbol ? to insert an incremental set of integers at any location. For instance:

system = powerSystem()

@bus(label = "Bus ? HV")
addBus!(system; type = 3, active = 0.1)
addBus!(system; type = 1, angle = -0.2)

@branch(label = "Branch ?")
addBranch!(system; from = "Bus 1 HV", to = "Bus 2 HV", reactance = 0.12)

@generator(label = "Generator ?")
addGenerator!(system; bus = "Bus 2 HV", active = 0.5, reactive = 0.1)

In this this example, two buses are generated and labeled as Bus 1 HV and Bus 2 HV, along with one branch and one generator labeled as Branch 1 and Generator 1, respectively.


Retrieving Labels

Finally, we will outline how users can retrieve stored labels. Let us consider the following power system creation:

system = powerSystem()

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

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

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

For instance, the bus labels can be accessed using the variable:

julia> system.bus.labelOrderedCollections.OrderedDict{String, Int64} with 3 entries:
  "Bus 2" => 1
  "Bus 1" => 2
  "Bus 3" => 3

If the objective is to obtain labels in the same order as the bus definitions sequence, users can utilize the following:

julia> label = collect(keys(system.bus.label))3-element Vector{String}:
 "Bus 2"
 "Bus 1"
 "Bus 3"

This approach can also be extended to branch and generator labels by making use of the variables present within the PowerSystem composite type, namely system.branch.label or system.generator.label.

Moreover, the from and to keywords associated with branches are stored based on internally assigned numerical values linked to bus labels. These values are stored in variables:

julia> [system.branch.layout.from system.branch.layout.to]2×2 Matrix{Int64}:
 1  2
 1  3

To recover the original from and to labels, we can utilize the following method:

julia> [label[system.branch.layout.from] label[system.branch.layout.to]]2×2 Matrix{String}:
 "Bus 2"  "Bus 1"
 "Bus 2"  "Bus 3"

Similarly, the bus keywords related to generators are saved based on internally assigned numerical values corresponding to bus labels and can be accessed using:

julia> system.generator.layout.bus2-element Vector{Int64}:
 2
 3

To recover the original bus labels, we can utilize the following method:

julia> label[system.generator.layout.bus]2-element Vector{String}:
 "Bus 1"
 "Bus 3"
Tip

JuliaGrid offers the capability to print labels alongside various types of data, such as power system parameters, voltages, powers, currents, or constraints used in optimal power flow analyses. For instance, users can use the following code to print labels in combination with specific data:

julia> print(system.branch.label, system.branch.parameter.reactance)Branch 2: 0.8
Branch 1: 0.5

AC and DC Model

When we constructed the power system, we can create an AC and/or DC model, which include vectors and matrices related to the power system's topology and parameters. The following code snippet demonstrates this:

system = powerSystem()

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

addBranch!(system; from = "Bus 1", to = "Bus 2", reactance = 0.12, shiftAngle = 0.1745)
addBranch!(system; from = "Bus 2", to = "Bus 3", resistance = 0.008, reactance = 0.05)

acModel!(system)
dcModel!(system)
Tip

In many instances throughout the JuliaGrid documentation, we explicitly mention these functions by their names, although it is not mandatory. If a user begins any of the various AC or DC analyses without having previously established the AC or DC model using the acModel! or dcModel! function, the respective function for setting the analysis will automatically create the AC or DC model.

The nodal matrices are one of the components of both the AC and DC models and are stored in the variables:

julia> system.model.ac.nodalMatrix3×3 SparseArrays.SparseMatrixCSC{ComplexF64, Int64} with 7 stored entries:
    0.0-8.33333im   -1.4468+8.20678im           ⋅
 1.4468+8.20678im   3.12012-27.8341im  -3.12012+19.5008im
        ⋅          -3.12012+19.5008im   3.12012-19.4508im
julia> system.model.dc.nodalMatrix3×3 SparseArrays.SparseMatrixCSC{Float64, Int64} with 7 stored entries: 8.33333 -8.33333 ⋅ -8.33333 28.3333 -20.0 ⋅ -20.0 20.0
Info

The AC model is used for performing AC power flow, AC optimal power flow, nonlinear state estimation, or state estimation with PMUs, whereas the DC model is essential for various DC or linear analyses. Consequently, once these models are developed, they can be applied to various types of simulations. We recommend that the reader refer to the tutorial on AC and DC models.


New Branch Triggers Model Update

We can execute the acModel! and dcModel! functions after defining the final number of buses, and each new branch added will trigger an update of the AC and DC vectors and matrices. Here is an example:

system = powerSystem()

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

acModel!(system)
dcModel!(system)

addBranch!(system; from = "Bus 1", to = "Bus 2", reactance = 0.12, shiftAngle = 0.1745)
addBranch!(system; from = "Bus 2", to = "Bus 3", resistance = 0.008, reactance = 0.05)

For example, the nodal matrix in the DC framework has the same values as before:

julia> system.model.dc.nodalMatrix3×3 SparseArrays.SparseMatrixCSC{Float64, Int64} with 7 stored entries:
  8.33333   -8.33333     ⋅
 -8.33333   28.3333   -20.0
   ⋅       -20.0       20.0
Tip

It is not fully recommended to create AC and DC models before adding a large number of branches if the execution time of functions is important. Instead, triggering updates to the AC and DC models using the addBranch! function is useful for power systems that require the addition of several branches. This update avoids the need to recreate vectors and matrices from scratch.


New Bus Triggers Model Erasure

The AC and DC models must be defined when a finite number of buses are defined, otherwise, adding a new bus will delete them. For example, if we attempt to add a new bus to the PowerSystem type that was previously created, the current AC and DC models will be completely erased:

julia> addBus!(system; label = "Bus 4", type = 2)[ Info: The current AC model field has been completely erased.
[ Info: The current DC model field has been completely erased.
julia> system.model.ac.nodalMatrix0×0 SparseArrays.SparseMatrixCSC{ComplexF64, Int64} with 0 stored entries
julia> system.model.dc.nodalMatrix0×0 SparseArrays.SparseMatrixCSC{Float64, Int64} with 0 stored entries

Update Bus

Once a bus has been added to the PowerSystem composite type, users have the flexibility to modify all parameters defined within the addBus! function. This means that when the updateBus! function is used, the PowerSystem type within AC and DC models that have been created is updated. This eliminates the need to recreate the AC and DC models from scratch.

To illustrate, let us consider the following power system:

system = powerSystem()

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

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

addGenerator!(system; label = "Generator 1", bus = "Bus 1", active = 0.5)
addGenerator!(system; label = "Generator 2", bus = "Bus 1", active = 0.2)

acModel!(system)
dcModel!(system)

For instance, the nodal matrix in the AC framework has the following form:

julia> system.model.ac.nodalMatrix3×3 SparseArrays.SparseMatrixCSC{ComplexF64, Int64} with 7 stored entries:
 0.01-8.33333im  -0.0+8.33333im       ⋅
  0.0+8.33333im   0.0-28.3333im  -0.0+20.0im
      ⋅           0.0+20.0im      0.0-19.95im

Now, let us add a shunt element to Bus 2:

updateBus!(system; label = "Bus 2", conductance = 0.4, susceptance = 0.5)

As we can observe, executing the function triggers an update of the AC nodal matrix:

julia> system.model.ac.nodalMatrix3×3 SparseArrays.SparseMatrixCSC{ComplexF64, Int64} with 7 stored entries:
 0.01-8.33333im  -0.0+8.33333im       ⋅
  0.0+8.33333im   0.4-27.8333im  -0.0+20.0im
      ⋅           0.0+20.0im      0.0-19.95im

Update Branch

Once a branch has been added to the PowerSystem composite type, users have the flexibility to modify all parameters defined within the addBranch! function. This means that when the updateBranch! function is used, the PowerSystem type within AC and DC models that have been created is updated. This eliminates the need to recreate the AC and DC models from scratch.

To illustrate, let us continue with the previous example and modify the parameters of Branch 1 as follows:

updateBranch!(system; label = "Branch 1", resistance = 0.012, reactance = 0.3)

We can observe the update in the AC nodal matrix:

julia> system.model.ac.nodalMatrix3×3 SparseArrays.SparseMatrixCSC{ComplexF64, Int64} with 7 stored entries:
  0.14312-3.32801im  -0.13312+3.32801im       ⋅
 -0.13312+3.32801im   0.53312-22.828im   -0.0+20.0im
          ⋅               0.0+20.0im      0.0-19.95im

Next, let us switch the status of Branch 2 from in-service to out-of-service:

updateBranch!(system; label = "Branch 2", status = 0)

As before, the updated AC nodal matrix takes the following form:

julia> system.model.ac.nodalMatrix3×3 SparseArrays.SparseMatrixCSC{ComplexF64, Int64} with 7 stored entries:
  0.14312-3.32801im  -0.13312+3.32801im      ⋅
 -0.13312+3.32801im   0.53312-2.82801im  0.0+0.0im
          ⋅               0.0+0.0im      0.0+0.05im

Drop Zeros

After the last execution of the updateBranch! function, the nodal matrices will contain zeros, as demonstrated in the code example. If needed, the user can remove these zeros using the dropZeros! function, as shown below:

dropZeros!(system.model.ac)
Info

It is worth mentioning that in simulations conducted with the JuliaGrid package, the precision of the outcomes remains unaffected even if zero entries are retained. However, we recommend users utilize this function instead of dropzeros! from the SuiteSparse package to ensure seamless functioning of all JuliaGrid functionalities.


Update Generator

Finally, users can update all generator parameters defined within the addGenerator! function using the updateGenerator! function. The execution of this function will affect all variables within the PowerSystem type.

In short, in addition to the generator field, JuliaGrid also retains variables associated with generators within the bus field. As an example, let us examine one of these variables and its values derived from a previous example:

julia> system.bus.supply.active3-element Vector{Float64}:
 0.7
 0.0
 0.0

Next, we will change the active output power of Generator 1:

updateGenerator!(system; label = "Generator 1", active = 0.9)

As we can see, executing the function triggers an update of the observed variable:

julia> system.bus.supply.active3-element Vector{Float64}:
 1.1
 0.0
 0.0

Hence, this function ensures the adjustment of generator parameters and updates all fields of the PowerSystem composite type affected by them.


Add and Update Costs

The cost! function is responsible for adding and updating costs associated with the active or reactive power produced by the corresponding generator. These costs are added only if the corresponding generator is defined.

To start, let us create an example of a power system using the following code:

system = powerSystem()

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

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

Polynomial Cost

Let us define a quadratic polynomial cost function for the active power produced by the Generator 1:

cost!(system; label = "Generator 1", active = 2, polynomial = [1100.0; 500.0; 150.0])

In essence, what we have accomplished is the establishment of a cost function depicted as $f(P_{\text{g}1}) = 1100 P_{\text{g}1}^2 + 500 P_{\text{g}1} + 150$ through the code provided. In general, when constructing a polynomial cost function, the coefficients must be ordered from the highest degree to the lowest.

The default input units are in per-units (pu), with coefficients of the cost function having units of currency/pu²hr for 1100, currency/puhr for 500, and currency/hr for 150. Therefore, the coefficients are stored exactly as entered:

julia> system.generator.cost.active.polynomial1-element Vector{Vector{Float64}}:
 [1100.0, 500.0, 150.0]

By setting active = 2 within the function, we express our intent to specify the active power cost using the active key. By using a value of 2, we signify our preference for employing a quadratic polynomial cost model for the associated generator. This flexibility proves invaluable when we have previously defined a piecewise linear cost function for the same generator. In such cases, we can set active = 1 to utilize the piecewise linear cost function to represent the cost of the corresponding generators. Thus, we retain the freedom to choose between these two cost functions according to the requirements of our simulation. Additionally, users have the option to define both piecewise and polynomial costs within a single function call, further enhancing the versatility of the implementation.


Piecewise Linear Cost

We can also create a piecewise linear cost function, for example, let us create the reactive power cost function for the same generator using the following code:

cost!(system; label = "Generator 1", reactive = 1, piecewise = [0.11 12.3; 0.15 16.8])

The first column denotes the generator's output reactive powers in per-units, while the second column specifies the corresponding costs for the specified reactive power in currency/hr. Thus, the data is stored exactly as entered:

julia> system.generator.cost.reactive.piecewise1-element Vector{Matrix{Float64}}:
 [0.11 12.3; 0.15 16.8]

Customizing Input Units for Keywords

Changing input units from per-units (pu) can be particularly useful since cost functions are usually related to SI units of powers. Let us set active powers in megawatts (MW) and reactive powers in megavolt-amperes reactive (MVAr) :

@power(MW, MVAr, pu)

Now, we can add the quadratic polynomial function using megawatts:

cost!(system; label = "Generator 1", active = 2, polynomial = [0.11; 5.0; 150.0])

After inspecting the resulting cost data, we can see that it is the same as before:

julia> system.generator.cost.active.polynomial1-element Vector{Vector{Float64}}:
 [1100.0, 500.0, 150.0]

Similarly, we can define the linear piecewise cost using megavolt-amperes reactive:

cost!(system; label = "Generator 1", reactive = 1, piecewise = [11.0 12.3; 15.0 16.8])

Upon inspection, we can see that the stored data is the same as before:

julia> system.generator.cost.reactive.piecewise1-element Vector{Matrix{Float64}}:
 [0.11 12.3; 0.15 16.8]
Tip

The cost! function not only adds costs but also allows users to update previously defined cost functions. This functionality is particularly valuable in optimal power flow analyses, as it allows users to modify generator power costs without the need to recreate models from scratch.