PTDF matrix

In this tutorial the methods for computing the Power Transfer Distribution Factors (PTDF) are presented. Before diving into this tutorial we encourage the user to load PowerNetworkMatrices, hit the ? key in the REPL terminal and look for the documention of the different PTDF methods available.

Evaluation of the PTDF matrix

The PTDF matrix can be evaluated according to two different approaches:

  • Dense: considers functions for dense matrix multiplication and inversion
  • KLU: considers functions for sparse matrix multiplication and inversion (default)

The evaluation of the PTDF matrix can be easily performed starting from importing the system's data and then by simply calling the PTDF method.

julia> using PowerNetworkMatrices
julia> using PowerSystemCaseBuilder
julia> const PNM = PowerNetworkMatricesPowerNetworkMatrices
julia> const PSB = PowerSystemCaseBuilderPowerSystemCaseBuilder
julia> sys = PSB.build_system(PSB.PSITestSystems, "c_sys5");[ Info: Loaded time series from storage file existing=/home/runner/.julia/packages/PowerSystemCaseBuilder/uZO8H/data/serialized_system/614e094ea985a55912fc1321256a49b755f9fc451c0f264f24d6d04af20e84d7/c_sys5_time_series_storage.h5 new=/tmp/jl_42We6i compression=InfrastructureSystems.CompressionSettings(false, InfrastructureSystems.CompressionTypesModule.CompressionTypes.DEFLATE = 1, 3, true)
julia> ptdf_1 = PTDF(sys);
julia> get_ptdf_data(ptdf_1)6×5 transpose(::Matrix{Float64}) with eltype Float64: 0.193917 -0.475895 -0.348989 0.0 0.159538 0.437588 0.258343 0.189451 0.0 0.36001 0.368495 0.217552 0.159538 0.0 -0.519548 0.193917 0.524105 -0.348989 0.0 0.159538 0.193917 0.524105 0.651011 0.0 0.159538 -0.368495 -0.217552 -0.159538 0.0 -0.480452

Advanced users might be interested in computing the PTDF matrix starting from either the data contained in the IncidenceMatrix and BA_matrix structures, or by the information related to the branches and buses of the system.

julia> # evaluate the BA_matrix and Incidence_Matrix
       ba_matrix = BA_Matrix(sys);
julia> a_matrix = IncidenceMatrix(sys); # get the PTDF matrix starting from the values of the # previosly cumputed matrices
julia> ptdf_2 = PTDF(a_matrix, ba_matrix);┌ Warning: PTDF creates via other matrices doesn't compute the subnetworks └ @ PowerNetworkMatrices ~/work/PowerNetworkMatrices.jl/PowerNetworkMatrices.jl/src/ptdf_calculations.jl:504
julia> get_ptdf_data(ptdf_2) # get the buses and branches of the system6×5 transpose(::Matrix{Float64}) with eltype Float64: 0.193917 -0.475895 -0.348989 0.0 0.159538 0.437588 0.258343 0.189451 0.0 0.36001 0.368495 0.217552 0.159538 0.0 -0.519548 0.193917 0.524105 -0.348989 0.0 0.159538 0.193917 0.524105 0.651011 0.0 0.159538 -0.368495 -0.217552 -0.159538 0.0 -0.480452
julia> branches = PNM.get_ac_branches(sys);
julia> buses = PNM.get_buses(sys);
julia> ptdf_3 = PTDF(branches, buses);
julia> get_ptdf_data(ptdf_3)6×5 transpose(::Matrix{Float64}) with eltype Float64: 0.193917 -0.475895 -0.348989 0.0 0.159538 0.437588 0.258343 0.189451 0.0 0.36001 0.368495 0.217552 0.159538 0.0 -0.519548 0.193917 0.524105 -0.348989 0.0 0.159538 0.193917 0.524105 0.651011 0.0 0.159538 -0.368495 -0.217552 -0.159538 0.0 -0.480452

NOTE: both the get_ac_branches and get_ac_branches functions are not exported by the PowerNetworkMatrices package, and therefore require the package name to be called as a prefix. However, they are shown here just for the sake of making an example.

Available methods for the computation of the PTDF matrix

As previously mentioned, the PTDF matrix can be evaluated considering different approaches. The method can be selected by specifying the field linear_solver in the PTDF function.

julia> ptdf_dense = PTDF(sys, linear_solver="Dense");
julia> get_ptdf_data(ptdf_dense)6×5 transpose(::Matrix{Float64}) with eltype Float64: 0.193917 -0.475895 -0.348989 0.0 0.159538 0.437588 0.258343 0.189451 0.0 0.36001 0.368495 0.217552 0.159538 0.0 -0.519548 0.193917 0.524105 -0.348989 0.0 0.159538 0.193917 0.524105 0.651011 0.0 0.159538 -0.368495 -0.217552 -0.159538 0.0 -0.480452
julia> ptdf_klu = PTDF(sys, linear_solver="KLU");
julia> get_ptdf_data(ptdf_klu)6×5 transpose(::Matrix{Float64}) with eltype Float64: 0.193917 -0.475895 -0.348989 0.0 0.159538 0.437588 0.258343 0.189451 0.0 0.36001 0.368495 0.217552 0.159538 0.0 -0.519548 0.193917 0.524105 -0.348989 0.0 0.159538 0.193917 0.524105 0.651011 0.0 0.159538 -0.368495 -0.217552 -0.159538 0.0 -0.480452

By default the "KLU" method is selected, which appeared to require significant less time and memory with respect to "Dense". Please note that either the KLU or Dense method is used, the resulting PTDF matrix is stored as a dense one.

Evaluating the PTDF matrix considering distributed slack bus

Whenever needed, the PTDF matrix can be computed with a distributed slack bus. To do so, a vector of type Vector{Float64} needs to be defined, specifying the weights for each bus of the system. These weights identify how the load on the slakc bus is redistributed accross the system.

julia> # consider equal distribution accross each bus for this example
       buscount = length(PNM.get_buses(sys));
julia> dist_slack = 1 / buscount * ones(buscount);
julia> dist_slack_array = dist_slack / sum(dist_slack);

Once the vector of the weights is defined, the PTDF matrix can be computed by defining the input argument dist_slack (empty array Float64[] by default):

julia> ptdf_distr = PTDF(sys, dist_slack=dist_slack_array);[ Info: Distributed bus

The difference between a the matrix computed with and without the dist_slack field defined can be seen as follows:

julia> # with no distributed slack bus
       get_ptdf_data(ptdf_klu)
       # with distributed slack bus6×5 transpose(::Matrix{Float64}) with eltype Float64:
  0.193917  -0.475895  -0.348989  0.0   0.159538
  0.437588   0.258343   0.189451  0.0   0.36001
  0.368495   0.217552   0.159538  0.0  -0.519548
  0.193917   0.524105  -0.348989  0.0   0.159538
  0.193917   0.524105   0.651011  0.0   0.159538
 -0.368495  -0.217552  -0.159538  0.0  -0.480452
julia> get_ptdf_data(ptdf_distr)6×5 transpose(::Matrix{Float64}) with eltype Float64: 0.288203 -0.381609 -0.254704 0.0942859 0.253824 0.18851 0.00926433 -0.0596271 -0.249079 0.110932 0.323288 0.172344 0.114331 -0.0452074 -0.564756 0.0882025 0.418391 -0.454704 -0.105714 0.0538239 -0.111797 0.218391 0.345296 -0.305714 -0.146176 -0.123288 0.0276555 0.0856694 0.245207 -0.235244

"Sparse" PTDF matrix

The PTFD matrix can be computed in a "sparse" fashion by defining the input argument tol. If this argument is defined, then elements of the PTDF matrix whose absolute values are below the set threshold are dropped. In addition, the matrix will be stored as a sparse one of type SparseArrays.SparseMatrixCSC{Float64, Int64} instead of Matrix{Float64}.

By considering an "extreme" value of 0.2 as tol, the PTDF matrix can be computed as follows:

julia> ptdf_sparse = PTDF(sys, tol=0.2);
julia> get_ptdf_data(ptdf_sparse)6×5 LinearAlgebra.Transpose{Float64, SparseArrays.SparseMatrixCSC{Float64, Int64}} with 15 stored entries: ⋅ -0.475895 -0.348989 ⋅ ⋅ 0.437588 0.258343 ⋅ ⋅ 0.36001 0.368495 0.217552 ⋅ ⋅ -0.519548 ⋅ 0.524105 -0.348989 ⋅ ⋅ ⋅ 0.524105 0.651011 ⋅ ⋅ -0.368495 -0.217552 ⋅ ⋅ -0.480452

NOTE: 0.2 was used for the purpose of this tutorial. In practice much smaller values are used (e.g., 1e-5).