VirtualPTDF

Contrary to the traditional PTDF matrix, the VirtualPTDF is a stucture contatining rows of the original matrix, related to specific system branches. The different rows of the PTDF matrix are cached in the VirtualPTDF structure as they are evaluated. This allows to keep just the portion of the original matrix which is of interest to the user, avoiding the unecessary computation of the whole matrix.

Refer to the different arguments of the VirtualPTDF methods by looking at the "Public API Reference" page.

How the VirtualPTDF works

The VirtualPTDF is a structure containing everything needed to compute any row of the PTDF matrix and store it. To do so, the VirtualPTDF must store the BA matrix (coming from the BA_Matrix struct) and the inverse of the ABA matrix (coming from ABA_MAtrix struct). In particular, KLU is used to get the LU factorization matrices of the ABA matrix and these ones are stored, avoid the inversion.

Once the VirtualPTDF is initialized, each row of the PTDF matrix can be evaluated separately. The algorithmic procedure is the following:

  1. Define the VirtualPTDF structure
  2. Call any element of the matrix to define and store the relative row as well as showing the selected element

Regarding point 2, if the row has been stored previosly then the desired element is just loaded from the cache and shown.

The flowchart below shows how the VirtualPTDF is structured and how it works. Examples will be presented in the following sections.

Initialize VirtualPTDF and compute/access row/element

As for the PTDF matrix, at first the System data must be loaded. The "RTS-GMLC" systems is considered as example:

julia> using PowerNetworkMatrices
julia> using PowerSystemCaseBuilder
julia> const PNM = PowerNetworkMatrices;
julia> const PSB = PowerSystemCaseBuilder;
julia> sys = PSB.build_system(PSB.PSISystems, "RTS_GMLC_DA_sys");[ Info: Loaded time series from storage file existing=/home/runner/.julia/packages/PowerSystemCaseBuilder/uZO8H/data/serialized_system/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/RTS_GMLC_DA_sys_time_series_storage.h5 new=/tmp/jl_AXBdXN compression=InfrastructureSystems.CompressionSettings(false, InfrastructureSystems.CompressionTypesModule.CompressionTypes.DEFLATE = 1, 3, true)

At this point the VirtualPTDF is initialized with the following simple command:

julia> v_ptdf = VirtualPTDF(sys);

Now, an element of the matrix can be computed by calling the branch name and bus number:

julia> el_C31_2_105 = v_ptdf["C31-2", 105]-0.005119964728130656

Alternatively, the number of the branch and bus (corresponding to the number of the PTDF row and column) can be used. In this case the row and column numbers are mapped by the dictonaries contained in the lookup field.

julia> row_number = v_ptdf.lookup[1]["C31-2"]112
julia> col_number = v_ptdf.lookup[2][105]5
julia> el_C31_2_105_bis = v_ptdf[row_number, col_number]-0.005119964728130656

NOTE: this example was made for the sake of completeness and considering the actual branch name and bus number is reccomended.

As previosly mentioned, in order to evaluate a single element of the VirtualPTDF, the entire row related to the selected branch must be considered. For this reason it is cached in the VirtualPTDF structure for later calls. This is evident by looking at the following example:

julia> sys_2k = PSB.build_system(PSB.PSYTestSystems, "tamu_ACTIVSg2000_sys");
julia> v_ptdf_2k = VirtualPTDF(sys_2k); # evaluate PTDF row related to branch "ODESSA 2 0 -1001-ODESSA 3 0 -1064-i_1"
julia> @time v_ptdf_2k["ODESSA 2 0 -1001-ODESSA 3 0 -1064-i_1", 8155] # call same element after the row has been stored 0.000102 seconds (11 allocations: 47.906 KiB) 5.0022477285046985e-5
julia> @time v_ptdf_2k["ODESSA 2 0 -1001-ODESSA 3 0 -1064-i_1", 8155] 0.000013 seconds (1 allocation: 16 bytes) 5.0022477285046985e-5

VirtualPTDF with distributed slack bus

As for the PTDF matrix, here too each row can be evaluated considering distibuted slack buses. A vector of type Vector{Float64} is defined, specifying the weights for each bus of the system.

julia> # smaller system for the next examples
       sys_2 = PSB.build_system(PSB.PSITestSystems, "c_sys5");
       
       # consider equal distribution accross each bus for this example[ 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_iSig0D compression=InfrastructureSystems.CompressionSettings(false, InfrastructureSystems.CompressionTypesModule.CompressionTypes.DEFLATE = 1, 3, true)
julia> buscount = length(PNM.get_buses(sys_2));
julia> dist_slack = 1 / buscount * ones(buscount);
julia> dist_slack_array = dist_slack / sum(dist_slack);

Now initialize the VirtualPTDF by defining the dist_slack field with the vector of weights previosly computed:

julia> v_ptdf_distr = VirtualPTDF(sys_2, dist_slack=dist_slack_array);[ Info: Distributed bus
julia> v_ptdf_orig = VirtualPTDF(sys_2);

Now check the difference with the same row related to the branch "1" evaluated without considering distributed slack bus.

julia> row_distr = [v_ptdf_distr["1", j] for j in v_ptdf_distr.axes[2]]5-element Vector{Float64}:
  0.28820251124689455
 -0.3816088095078225
 -0.2547035520042974
  0.09428590613039686
  0.25382394413482845
julia> row_original = [v_ptdf_orig["1", j] for j in v_ptdf_orig.axes[2]]5-element Vector{Float64}: 0.19391660511649766 -0.47589471563821933 -0.34898945813469423 0.0 0.15953803800443161

"Sparse" VirtualPTDF

Sparsification of each row can be achieved in the same fashion as for the PTDF matrix, by removing those elements whose absolute values is below a certain threshold.

As for the example show for the PTDF matrix, here to a very high values of 0.2 is considered for the tol field. Again, this value is considered just for the sake of this example.

julia> v_ptdf_dense = VirtualPTDF(sys_2);
julia> v_ptdf_sparse = VirtualPTDF(sys_2, tol=0.2);

Let's now evaluate the same row as before and compare the results:

julia> original_row = [v_ptdf_dense["1", j] for j in v_ptdf_dense.axes[2]]5-element Vector{Float64}:
  0.19391660511649766
 -0.47589471563821933
 -0.34898945813469423
  0.0
  0.15953803800443161
julia> sparse_row = [v_ptdf_sparse["1", j] for j in v_ptdf_sparse.axes[2]]5-element Vector{Float64}: 0.0 -0.47589471563821933 -0.34898945813469423 0.0 0.0