AC State Estimation
To perform nonlinear or AC state estimation, the initial requirement is to have the PowerSystem
type configured with the AC model, along with the Measurement
type storing measurement data. Next, we can develop either the weighted least-squares (WLS) model, utilizing the Gauss-Newton method, or the least absolute value (LAV) model. These models are encapsulated within the AcStateEstimation
type:
To obtain bus voltages and solve the state estimation problem, users need to implement the Gauss-Newton iterative process for the WLS model using:
Alternatively, to obtain the LAV estimator, simply execute the second function.
After solving the AC state estimation, JuliaGrid provides functions for computing powers and currents:
Alternatively, instead of designing their own iteration process for the Gauss-Newton method or using the function responsible for solving the LAV model, and computing powers and currents, users can use the wrapper function:
Users can also access specialized functions for computing specific types of powers or currents for individual buses, branches, or generators within the power system.
Bus Type Modification
In AC state estimation, it is necessary to designate a slack bus, where the bus voltage angle is known. Therefore, when establishing the AcStateEstimation
type, the initially assigned slack bus is evaluated and may be altered. If the designated slack bus (type = 3
) lacks a connected in-service generator, it will be changed to a demand bus (type = 1
). Conversely, the first generator bus (type = 2
) with an active in-service generator linked to it will be reassigned as the new slack bus (type = 3
).
Weighted Least-Squares Estimator
To begin, we will define the PowerSystem
and Measurement
types:
system, monitoring = ems()
addBus!(system; label = "Bus 1", type = 3)
addBus!(system; label = "Bus 2", type = 1, active = 0.6, reactive = 0.1)
addBus!(system; label = "Bus 3", type = 1, active = 0.5, reactive = 0.2)
@branch(resistance = 0.14, conductance = 1e-4, susceptance = 0.04)
addBranch!(system; label = "Branch 1", from = "Bus 1", to = "Bus 2", reactance = 0.25)
addBranch!(system; label = "Branch 2", from = "Bus 1", to = "Bus 3", reactance = 0.35)
addBranch!(system; label = "Branch 3", from = "Bus 2", to = "Bus 3", reactance = 0.16)
addGenerator!(system; label = "Generator 1", bus = "Bus 1", active = 1.2, reactive = 0.3)
@voltmeter(label = "Voltmeter ? (!)")
addVoltmeter!(monitoring; bus = "Bus 1", magnitude = 1.0, variance = 1e-5)
@ammeter(label = "Ammeter ? (!)")
addAmmeter!(monitoring; from = "Branch 3", magnitude = 0.0356, variance = 1e-3)
addAmmeter!(monitoring; to = "Branch 2", magnitude = 0.5892, variance = 1e-3)
@wattmeter(label = "Wattmeter ? (!)")
addWattmeter!(monitoring; from = "Branch 1", active = 0.7067, variance = 1e-4)
addWattmeter!(monitoring; bus = "Bus 2", active = -0.6, variance = 2e-4)
@varmeter(label = "Varmeter ? (!)")
addVarmeter!(monitoring; from = "Branch 1", reactive = 0.2125, variance = 1e-4)
addVarmeter!(monitoring; bus = "Bus 2", reactive = -0.1, variance = 1e-5)
Next, to establish the AC state estimation model, we will utilize the gaussNewton
function:
analysis = gaussNewton(monitoring)
Here, the user triggers LU factorization as the default method for solving the system of linear equations within each iteration of the Gauss-Newton method. However, the user also has the option to select alternative factorization methods such as LDLt
or QR
:
analysis = gaussNewton(monitoring, LDLt)
Setup Initial Voltages
The initial voltages for the Gauss-Newton method are determined based on the specified initial voltage magnitudes and angles within the buses of the PowerSystem
type. These values are then forwarded to the AcStateEstimation
during the execution of the gaussNewton
function. Therefore, the initial voltages in this example are as follows:
julia> print(system.bus.label, analysis.voltage.magnitude, analysis.voltage.angle)
Bus 1: 1.0, 0.0 Bus 2: 1.0, 0.0 Bus 3: 1.0, 0.0
Users have the flexibility to modify these vectors according to their own requirements in order to adjust the initial voltages. For instance, users can conduct an AC power flow analysis and utilize the obtained solution as the initial voltages for AC state estimation:
powerFlow = newtonRaphson(system)
for iteration = 1:10
mismatch!(powerFlow)
solve!(powerFlow)
end
setInitialPoint!(analysis, powerFlow)
State Estimator
To conduct an iterative process using the Gauss-Newton method, it is essential to include the increment!
and solve!
functions inside the iteration loop. For example:
for iteration = 1:20
increment!(analysis)
solve!(analysis)
end
Once the state estimator is obtained, users can access the bus voltage magnitudes and angles using:
julia> print(system.bus.label, analysis.voltage.magnitude, analysis.voltage.angle)
Bus 1: 0.9999999893867697, 0.0 Bus 2: 0.8551739067243623, -0.16932686045421688 Bus 3: 0.8460326304864767, -0.17123533032549176
Breaking the Iterative Process
The iterative process can be terminated using the increment!
function. The following code demonstrates how to utilize this function to break out of the iteration loop:
analysis = gaussNewton(monitoring)
for iteration = 1:20
stopping = increment!(analysis)
if stopping < 1e-8
println("Solution found in $(analysis.method.iteration) iterations.")
break
end
solve!(analysis)
end
Solution found in 4 iterations.
The increment!
function returns the maximum absolute values of the state variable increment, which are commonly used as a convergence criterion in the iterative Gauss-Newton algorithm.
Readers can refer to the AC State Estimation tutorial for implementation insights.
Wrapper Function
JuliaGrid provides a wrapper function stateEstimation!
for AC state estimation. Hence, it offers a way to solve it using the Gauss-Newton method with reduced implementation effort:
analysis = gaussNewton(monitoring)
stateEstimation!(analysis; verbose = 3)
Number of wattmeters: 2 Number of varmeters: 2 Number of voltmeters: 1
In-service: 2 In-service: 2 In-service: 1
Out-of-service: 0 Out-of-service: 0 Out-of-service: 0
Number of ammeters: 2 Number of PMUs: 0 Number of devices: 7
In-service: 2 In-service: 0 In-service: 7
Out-of-service: 0 Out-of-service: 0 Out-of-service: 0
Number of entries in the Jacobian: 29
Number of measurement functions: 7
Number of state variables: 5
Number of buses: 3
Number of branches: 3
-----------------------------------------------
Iteration Objective Value Maximum Increment
-----------------------------------------------
0 8.01893670e+03 2.03582921e-01
1 2.97107558e+02 4.82737240e-02
2 2.16194897e-01 1.87147684e-03
3 4.71061964e-06 2.58855232e-06
4 3.54147541e-07 1.58001739e-10
Measurement Maximum Value
Absolute Residual: 2 1.2391e-05
Objective Value: 2 1.5352e-07
EXIT: The solution was found using the Gauss-Newton method in 4 iterations.
Users can choose any approach in this section to obtain the WLS estimator based on their needs. Additionally, users can review the Gauss-Newton Algorithm used in the wrapper function within the tutorial section.
Inclusion of PMUs in Rectangular Coordinates
In the example above, our focus is solely on solving the AC state estimation using SCADA measurements. However, users have the option to also integrate PMUs into the AC state estimation, either in the rectangular or polar coordinate system.
The default approach is to include PMUs in the rectangular coordinate system:
@pmu(label = "PMU ? (!)")
addPmu!(monitoring; to = "Branch 1", magnitude = 0.7466, angle = 2.8011)
In the case of the rectangular system, inclusion resolves ill-conditioned problems arising in polar coordinates due to small values of current magnitudes. However, this approach's main disadvantage is related to measurement errors, as measurement errors correspond to polar coordinates. Therefore, the covariance matrix must be transformed from polar to rectangular coordinates [3]. As a result, measurement errors of a single PMU are correlated, and the covariance matrix does not have a diagonal form. Despite that, the measurement error covariance matrix is usually considered as a diagonal matrix, affecting the accuracy of the state estimation.
In the example above, we specifically include PMUs where measurement error correlations are disregarded. This is evident through the precision matrix, which maintains a diagonal form:
julia> analysis = gaussNewton(monitoring);
julia> analysis.method.precision
9×9 SparseArrays.SparseMatrixCSC{Float64, Int64} with 9 stored entries: 100000.0 ⋅ ⋅ ⋅ ⋅ … ⋅ ⋅ ⋅ ⋅ 1000.0 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1000.0 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 10000.0 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 5000.0 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ … ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 100000.0 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1.05192e8 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1.64807e8
Lastly, we incorporate correlation into our model by adding a new PMU with the desired error correlation:
addPmu!(monitoring; bus = "Bus 3", magnitude = 0.846, angle = -0.1712, correlated = true)
Now, we can observe the precision matrix that does not hold a diagonal form:
julia> analysis = gaussNewton(monitoring);
julia> analysis.method.precision
11×11 SparseArrays.SparseMatrixCSC{Float64, Int64} with 13 stored entries: 100000.0 ⋅ ⋅ ⋅ … ⋅ ⋅ ⋅ ⋅ 1000.0 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1000.0 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 10000.0 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ … ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1.64807e8 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1.01153e8 6.66801e6 ⋅ ⋅ ⋅ ⋅ … ⋅ 6.66801e6 1.38567e8
Inclusion of PMUs in Polar Coordinates
The second approach involves incorporating these measurements into the polar coordinate system. For instance:
addPmu!(monitoring; from = "Branch 1", magnitude = 0.7379, angle = -0.2921, polar = true)
This inclusion of PMUs provides more accurate state estimates compared to rectangular inclusion, but demands longer computing time. PMUs are handled in the same manner as SCADA measurements. However, this approach is susceptible to ill-conditioned problems arising in polar coordinates due to small values of current magnitudes [3, 4].
It is important to note that with each individual phasor measurement, we can set the coordinate system, providing flexibility to include some in polar and some in rectangular systems. This flexibility is particularly valuable because bus voltage phasor measurements are preferably included in a polar coordinate system, while current phasor measurements are best suited to a rectangular coordinate system.
Alternative Formulation
The resolution of the WLS state estimation problem using the conventional method typically progresses smoothly. However, it is widely acknowledged that in certain situations common to real-world systems, this method can be vulnerable to numerical instabilities. Such conditions might impede the algorithm from finding a satisfactory solution. In such cases, users may opt for an alternative formulation of the WLS state estimation, namely, employing an approach called orthogonal method [5, Sec. 3.2].
This approach is suitable when measurement errors are uncorrelated, and the precision matrix remains diagonal. Therefore, as a preliminary step, we need to eliminate the correlation, as we did previously:
updatePmu!(monitoring; label = "PMU 2 (Bus 3)", correlated = false)
Subsequently, by specifying the Orthogonal
argument in the gaussNewton
function, JuliaGrid implements a more robust approach to obtain the WLS estimator, which proves particularly beneficial when substantial differences exist among measurement variances:
analysis = gaussNewton(monitoring, Orthogonal)
stateEstimation!(analysis; verbose = 1)
EXIT: The solution was found using the Gauss-Newton method in 7 iterations.
Readers can refer to the Alternative Formulation tutorial for implementation insights.
Print Results in the REPL
Users have the option to print the results in the REPL using any units that have been configured, such as:
@voltage(pu, deg)
printBusData(analysis)
|-----------------------------|
| Bus Data |
|-----------------------------|
| Label | Voltage |
| | |
| Bus | Magnitude | Angle |
| | [pu] | [deg] |
|-------|-----------|---------|
| Bus 1 | 0.9999 | 0.0000 |
| Bus 2 | 0.8551 | -9.7028 |
| Bus 3 | 0.8460 | -9.8090 |
|-----------------------------|
Next, users can easily 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 as follows:
open("bus.txt", "w") do file
printBusData(analysis, file)
end
We also provide functions to print or save state estimation results, such as estimated values and residuals. For more details, users can consult the Power and Current Analysis section of this manual.
Least Absolute Value Estimator
The LAV method presents an alternative estimation technique known for its increased robustness compared to WLS. While the WLS method relies on specific assumptions regarding measurement errors, robust estimators like LAV are designed to maintain unbiasedness even in the presence of various types of measurement errors and outliers. This characteristic often eliminates the need for extensive bad data analysis procedures [5, Ch. 6]. However, it is important to note that achieving robustness typically involves increased computational complexity.
To obtain an LAV estimator, users need to employ one of the solvers listed in the JuMP documentation. In many common scenarios, the Ipopt
solver proves sufficient to obtain a solution:
using Ipopt
analysis = acLavStateEstimation(monitoring, Ipopt.Optimizer; verbose = 1)
Setup Initial Primal Values
In JuliaGrid, the assignment of initial primal values for optimization variables takes place when the solve!
function is executed. Initial primal values are determined based on the voltage
fields within the AcStateEstimation
type. By default, these values are established using the initial bus voltage magnitudes and angles from PowerSystem
type:
julia> print(system.bus.label, analysis.voltage.magnitude, analysis.voltage.angle)
Bus 1: 1.0, 0.0 Bus 2: 1.0, 0.0 Bus 3: 1.0, 0.0
Users have the flexibility to customize these values according to their requirements, and they will be utilized as the initial primal values when executing the solve!
function. Additionally, the setInitialPoint! function allows users to configure the initial point as required.
Solution
To solve the formulated LAV state estimation model, simply execute the following function:
solve!(analysis)
EXIT: The optimal solution was found.
Upon obtaining the solution, access the bus voltage magnitudes and angles using:
julia> print(system.bus.label, analysis.voltage.magnitude, analysis.voltage.angle)
Bus 1: 0.9999955853356097, 0.0 Bus 2: 0.8551679167453302, -0.16932742195977238 Bus 3: 0.8460243712346344, -0.1712374725408435
Readers can refer to the Least Absolute Value Estimation tutorial for implementation insights.
Measurement Set Update
After establishing the Measurement
type using the measurement
function, users gain the capability to incorporate new measurement devices or update existing ones.
Once updates are completed, users can seamlessly progress towards generating the AcStateEstimation
type using the gaussNewton
or acLavStateEstimation
function and solving the AC state estimation:
system, monitoring = ems() # <- Initialize the Measurement instance
addBus!(system; label = "Bus 1", type = 3)
addBus!(system; label = "Bus 2", type = 1, active = 0.6, reactive = 0.1)
addBus!(system; label = "Bus 3", type = 1, active = 0.5, reactive = 0.2)
@branch(resistance = 0.14, conductance = 1e-4, susceptance = 0.04)
addBranch!(system; label = "Branch 1", from = "Bus 1", to = "Bus 2", reactance = 0.25)
addBranch!(system; label = "Branch 2", from = "Bus 1", to = "Bus 3", reactance = 0.35)
addBranch!(system; label = "Branch 3", from = "Bus 2", to = "Bus 3", reactance = 0.16)
addGenerator!(system; label = "Generator 1", bus = "Bus 1", active = 1.2, reactive = 0.3)
@voltmeter(label = "Voltmeter ? (!)", variance = 1e-3)
addVoltmeter!(monitoring; bus = "Bus 1", magnitude = 1.0)
@wattmeter(label = "Wattmeter ? (!)", varianceBus = 1e-2)
addWattmeter!(monitoring; from = "Branch 1", active = 0.7067)
addWattmeter!(monitoring; bus = "Bus 2", active = -0.6)
@varmeter(label = "Varmeter ? (!)", varianceFrom = 1e-3)
addVarmeter!(monitoring; from = "Branch 1", reactive = 0.2125)
addVarmeter!(monitoring; bus = "Bus 2", reactive = -0.1)
@pmu(label = "PMU ? (!)")
addPmu!(monitoring; bus = "Bus 2", magnitude = 0.8552, angle = -0.1693)
analysis = gaussNewton(monitoring) # <- Build AcStateEstimation for the defined model
stateEstimation!(analysis)
addWattmeter!(monitoring; from = "Branch 3", active = 0.0291)
updateWattmeter!(monitoring; label = "Wattmeter 2 (Bus 2)", variance = 1e-4)
addVarmeter!(monitoring; to = "Branch 3", reactive = -0.037, variance = 1e-5)
updateVarmeter!(monitoring; label = "Varmeter 2 (Bus 2)", reactive = -0.11)
updatePmu!(monitoring; label = "PMU 1 (Bus 2)", polar = false)
analysis = gaussNewton(monitoring) # <- Build AcStateEstimation for the updated model
stateEstimation!(analysis)
This concept removes the need to restart and recreate the Measurement
type from the beginning when implementing changes to the existing measurement set.
State Estimation Update
An advanced methodology involves users establishing the AcStateEstimation
type using gaussNewton
or acLavStateEstimation
just once. After this initial setup, users can seamlessly modify existing measurement devices without the need to recreate the AcStateEstimation
type.
This advancement extends beyond the previous scenario where recreating the Measurement
type was unnecessary, to now include the scenario where AcStateEstimation
also does not need to be recreated.
The addition of new measurements after the creation of AcStateEstimation
is not practical in terms of reusing this type. Instead, we recommend that users create a final set of measurements and then utilize update functions to manage devices, either putting them in-service or out-of-service throughout the process.
We can modify the prior example to achieve the same model without establishing AcStateEstimation
twice:
system, monitoring = ems() # <- Initialize the Measurement instance
addBus!(system; label = "Bus 1", type = 3)
addBus!(system; label = "Bus 2", type = 1, active = 0.6, reactive = 0.1)
addBus!(system; label = "Bus 3", type = 1, active = 0.5, reactive = 0.2)
@branch(resistance = 0.14, conductance = 1e-4, susceptance = 0.04)
addBranch!(system; label = "Branch 1", from = "Bus 1", to = "Bus 2", reactance = 0.25)
addBranch!(system; label = "Branch 2", from = "Bus 1", to = "Bus 3", reactance = 0.35)
addBranch!(system; label = "Branch 3", from = "Bus 2", to = "Bus 3", reactance = 0.16)
addGenerator!(system; label = "Generator 1", bus = "Bus 1", active = 1.2, reactive = 0.3)
@voltmeter(label = "Voltmeter ? (!)")
addVoltmeter!(monitoring; bus = "Bus 1", magnitude = 1.0)
@wattmeter(label = "Wattmeter ? (!)", varianceBus = 1e-2)
addWattmeter!(monitoring; from = "Branch 1", active = 0.7067)
addWattmeter!(monitoring; bus = "Bus 2", active = -0.6)
addWattmeter!(monitoring; from = "Branch 3", active = 0.0291, status = 0)
@varmeter(label = "Varmeter ? (!)", varianceFrom = 1e-3)
addVarmeter!(monitoring; from = "Branch 1", reactive = 0.2125)
addVarmeter!(monitoring; bus = "Bus 2", reactive = -0.1)
addVarmeter!(monitoring; to = "Branch 3", reactive = -0.037, variance = 1e-5, status = 0)
@pmu(label = "PMU ? (!)")
addPmu!(monitoring; bus = "Bus 2", magnitude = 0.8552, angle = -0.1693)
analysis = gaussNewton(monitoring) # <- Build AcStateEstimation for the defined model
stateEstimation!(analysis)
updateWattmeter!(analysis; label = "Wattmeter 3 (From Branch 3)", status = 1)
updateWattmeter!(analysis; label = "Wattmeter 2 (Bus 2)", variance = 1e-4)
updateVarmeter!(analysis; label = "Varmeter 3 (To Branch 3)", status = 1)
updateVarmeter!(analysis; label = "Varmeter 2 (Bus 2)", reactive = -0.11)
updatePmu!(analysis; label = "PMU 1 (Bus 2)", polar = false)
# <- No need for re-build; we have already updated the existing AcStateEstimation instance
stateEstimation!(analysis)
This concept removes the need to rebuild both the Measurement
and the AcStateEstimation
from the beginning when implementing changes to the existing measurement set. In the scenario of employing the WLS model, JuliaGrid can reuse the symbolic factorizations of LU or LDLt, provided that the nonzero pattern of the gain matrix remains unchanged.
Power and Current Analysis
After obtaining the solution from the AC state estimation, we can calculate various electrical quantities related to buses and branches using the power!
and current!
functions. For instance, let us consider the model for which we obtained the AC state estimation solution:
system, monitoring = ems()
addBus!(system; label = "Bus 1", type = 3, susceptance = 0.002)
addBus!(system; label = "Bus 2", type = 1, active = 0.1, reactive = 0.01)
addBus!(system; label = "Bus 3", type = 1, active = 2.5, reactive = 0.2)
@branch(resistance = 0.02, conductance = 1e-4, susceptance = 0.04)
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.05)
addBranch!(system; label = "Branch 3", from = "Bus 2", to = "Bus 3", reactance = 0.03)
addGenerator!(system; label = "Generator 1", bus = "Bus 1", active = 3.2, reactive = 0.3)
addWattmeter!(monitoring; from = "Branch 1", active = 1.046, variance = 1e-2)
addWattmeter!(monitoring; bus = "Bus 2", active = -0.1, variance = 1e-3)
addWattmeter!(monitoring; from = "Branch 3", active = 0.924, variance = 1e-3)
addVarmeter!(monitoring; from = "Branch 1", reactive = 0.059, variance = 1e-3)
addVarmeter!(monitoring; bus = "Bus 2", reactive = -0.01, variance = 1e-2)
addVarmeter!(monitoring; to = "Branch 3", reactive = -0.044, variance = 1e-3)
analysis = gaussNewton(monitoring)
stateEstimation!(analysis; verbose = 1)
EXIT: The solution was found using the Gauss-Newton method in 6 iterations.
We can now utilize the provided functions to compute powers and currents:
power!(analysis)
current!(analysis)
For instance, if we want to show the active power injections and the from-bus current angles, we can employ the following code:
julia> print(system.bus.label, analysis.power.injection.active)
Bus 1: 2.6951392544228305 Bus 2: -0.09999730840051714 Bus 3: -2.4992878004905723
julia> print(system.branch.label, analysis.current.from.angle)
Branch 1: -0.056343895391946154 Branch 2: -0.15413096831549436 Branch 3: -0.08788060834432551
To better understand the powers and currents associated with buses and branches that are calculated by the power!
and current!
functions, we suggest referring to the tutorials on AC State Estimation.
Print Results in the REPL
Users can utilize any of the print functions outlined in the Print API. For example, to print state estimation data related to wattmeters, we can use:
@power(MW, pu)
printWattmeterData(analysis)
|---------------------------------------------------------------|
| Wattmeter Data |
|---------------------------------------------------------------|
| Label | Active Power |
| | |
| | Measurement | Variance | Estimate | Residual | Status |
| | [MW] | [MW] | [MW] | [MW] | |
|-------|-------------|----------|----------|----------|--------|
| 1 | 104.6000 | 1.00e+00 | 104.6026 | -0.0026 | 1 |
| 2 | -10.0000 | 1.00e-01 | -9.9997 | -0.0003 | 1 |
| 3 | 92.4000 | 1.00e-01 | 92.3997 | 0.0003 | 1 |
|---------------------------------------------------------------|
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()
printWattmeterData(analysis, io; style = false)
CSV.write("bus.csv", CSV.File(take!(io); delim = "|"))
Active and Reactive Power Injection
To calculate the active and reactive power injection associated with a specific bus, the function can be used:
julia> active, reactive = injectionPower(analysis; label = "Bus 1")
(2.6951392544228305, 0.31320449720691024)
Active and Reactive Power Injection from Generators
To calculate the active and reactive power injection from the generators at a specific bus, the function can be used:
julia> active, reactive = supplyPower(analysis; label = "Bus 1")
(2.6951392544228305, 0.31320449720691024)
Active and Reactive Power at Shunt Element
To calculate the active and reactive power associated with shunt element at a specific bus, the function can be used:
julia> active, reactive = shuntPower(analysis; label = "Bus 1")
(0.0, -0.0020066379178127042)
Active and Reactive Power Flow
Similarly, we can compute the active and reactive power flow at both the from-bus and to-bus ends of the specific branch by utilizing the provided functions below:
julia> active, reactive = fromPower(analysis; label = "Branch 2")
(1.6491130619728873, 0.25621149744636623)
julia> active, reactive = toPower(analysis; label = "Branch 2")
(-1.5932872124411979, -0.15530958335763043)
Active and Reactive Power at Charging Admittances
To calculate the active and reactive power linked with branch charging admittances of the particular branch, the function can be used:
julia> active, reactive = chargingPower(analysis; label = "Branch 1")
(9.80036319844151e-5, -0.03920145279376604)
Active powers indicate active losses within the branch's charging admittances. Moreover, charging admittances injected reactive powers into the power system due to their capacitive nature, as denoted by a negative sign.
Active and Reactive Power at Series Impedance
To calculate the active and reactive power across the series impedance of the branch, the function can be used:
julia> active, reactive = seriesPower(analysis; label = "Branch 2")
(0.05572979310997467, 0.13932448277493664)
The active power also considers active losses originating from the series resistance of the branch, while the reactive power represents reactive losses resulting from the impedance's inductive characteristics.
Current Injection
To calculate the current injection associated with a specific bus, the function can be used:
julia> magnitude, angle = injectionCurrent(analysis; label = "Bus 1")
(2.7087856562191526, -0.1156919344469013)
Current Flow
We can compute the current flow at both the from-bus and to-bus ends of the specific branch by utilizing the provided functions below:
julia> magnitude, angle = fromCurrent(analysis; label = "Branch 2")
(1.6661346814754245, -0.15413096831549436)
julia> magnitude, angle = toCurrent(analysis; label = "Branch 2")
(1.6709804259856276, 2.964170792385997)
Current Through Series Impedance
To calculate the current passing through the series impedance of the branch in the direction from the from-bus end to the to-bus end, we can use the following function:
julia> magnitude, angle = seriesCurrent(analysis; label = "Branch 2")
(1.6692781839761557, -0.1659946761684348)