make ptdf diag lazy#304
Conversation
There was a problem hiding this comment.
Pull request overview
This PR improves VirtualMODF construction performance by avoiding eager computation of the diag(PTDF·A) vector and instead computing/caching it on first access. It also adds a faster implementation of _get_PTDF_A_diag and extends tests/docs around MODF/LODF correctness and the new lazy behavior.
Changes:
- Make
VirtualMODF.PTDF_A_diaglazily computed via agetpropertyhook with caching + logging. - Optimize
_get_PTDF_A_diagby exploiting incidence sparsity (two nonzeros per row) and reduced-index mapping. - Add N-2 MODF identity tests, lazy-caching/logging tests, and documentation for
VirtualMODF.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/virtual_modf_calculations.jl |
Implements lazy PTDF_A_diag computation/caching via getproperty; removes eager precompute in constructor. |
src/virtual_lodf_calculations.jl |
Reworks _get_PTDF_A_diag to a faster sparse-aware implementation and loosens K typing. |
test/test_virtual_modf.jl |
Adds N-2 MODF vs LODF/PTDF identity checks, lazy-diag logging/cache behavior tests, and an N-2 islanding pseudoinverse ground-truth check. |
test/test_virtual_lodf.jl |
Adds correctness test comparing _get_PTDF_A_diag to a slower reference implementation. |
test/test_modf_lodf_reductions.jl |
Adds helper + test to validate N-2 identity under network reductions. |
docs/src/reference/network_matrices_overview.md |
Documents VirtualMODF conceptually and adds it to the axes overview table. |
docs/src/how_to_guides/compute_network_matrices.md |
Adds a “Computing Virtual MODF Matrix” usage example and updates the axes table. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for i in 1:n_branches | ||
| # Extract BA column for valid indices | ||
| fill!(ba_col, 0.0) | ||
| for idx in 1:n_valid | ||
| bus_idx = valid_ix[idx] | ||
| ba_col[idx] = BA[bus_idx, i] | ||
| @inbounds for k in SparseArrays.nzrange(BA, i) | ||
| valid_i = bus_to_valid_idx[ba_rv[k]] | ||
| valid_i > 0 || continue | ||
| ba_col[valid_i] = ba_nz[k] | ||
| end | ||
|
|
||
| solve!(K, ba_col) | ||
| _solve_factorization(K, ba_col) | ||
|
|
||
| # Map back to full bus indices | ||
| fill!(ptdf_row, 0.0) | ||
| for idx in 1:n_valid | ||
| ptdf_row[valid_ix[idx]] = ba_col[idx] | ||
| # ba_col is now PTDF row i in valid-index space; H[e,e] = ptdf[from] - ptdf[to]. | ||
| f = arc_from_valid[i] | ||
| t = arc_to_valid[i] | ||
| v_f = if f > 0 | ||
| ba_col[f] | ||
| else | ||
| 0.0 | ||
| end | ||
|
|
||
| # Compute diagonal element: sum of PTDF[i,j] * A[i,j] for all buses j | ||
| for j in 1:n_buses | ||
| diag_[i] += ptdf_row[j] * A[i, j] | ||
| v_t = if t > 0 | ||
| ba_col[t] | ||
| else | ||
| 0.0 | ||
| end | ||
| @inbounds diag_[i] = v_f - v_t | ||
| end |
There was a problem hiding this comment.
I think this is correct; it seems like this will not work for all backends
There was a problem hiding this comment.
Right now we only support KLU as a backend for MODF. We can address this when merging #302
| !isempty(diag) && return diag | ||
| @lock getfield(vmodf, :solver_lock) begin | ||
| diag = getfield(vmodf, :PTDF_A_diag) | ||
| !isempty(diag) && return diag | ||
| n_arcs = length(getfield(vmodf, :axes)[1]) |
Performance ResultsPrecompile Time
Execution Time
|
This PR makes the calculation of the MODF faster by making the PTDF diagonal calculation lazy which is only needed for certain calculations like potential for islanding and not for all cases.
These changes prevent populating the whole diagonal by default and reduce the construction times.
Issues with construction times were reported by @yonghongchen8 when using the EI
It also extends the testing and docs for VirtualMODF