Skip to content

Extract shared VirtualFactorCore from virtual matrices#313

Merged
jd-lara merged 6 commits into
psy6from
claude/merge-modf-ptfd-struct-Og8R3
Jun 2, 2026
Merged

Extract shared VirtualFactorCore from virtual matrices#313
jd-lara merged 6 commits into
psy6from
claude/merge-modf-ptfd-struct-Og8R3

Conversation

@jd-lara
Copy link
Copy Markdown
Member

@jd-lara jd-lara commented Jun 2, 2026

Context

VirtualPTDF, VirtualMODF, and VirtualLODF each independently built and stored the same DC factorization (K/BA/A/ABA) and topology. PTDF and MODF additionally computed post-contingency PTDF rows through the same Woodbury kernel via two byte-for-byte duplicate outer wrappers, and LODF/MODF both recomputed the identical PTDF_A_diag (one solve per arc) and branch_susceptances_by_arc. Needing all three meant factorizing three times and reconciling their topology/reductions.

What this does

Introduces a private, unexported VirtualFactorCore that owns the factorization, topology, solve scratch, solver lock, and the lazily-computed shared PTDF_A_diag / branch_susceptances_by_arc. The three virtual matrices become thin wrappers that hold a core and forward to it via accessors and a getproperty shim — so all existing public APIs (getindex, accessors, get_post_modification_ptdf_row, …) keep working unchanged, with no deprecations.

A single core can now be shared so the ABA matrix is factorized once:

vptdf = VirtualPTDF(Ybus(sys))
vmodf = VirtualMODF(vptdf, sys)   # shares vptdf's core
vlodf = VirtualLODF(vptdf)        # shares vptdf's core

This gives PowerSimulations a single object to build once and prevents reconciliation drift between the matrices.

Changes

  • New src/virtual_factor_core.jl: the VirtualFactorCore{Ax,L,K} struct, accessors, lazy get_PTDF_A_diag / get_branch_susceptances_by_arc, and the Ybus constructor.
  • New src/virtual_factor_helpers.jl: relocates _create_factorization, _solve_factorization, _extract_arc_susceptances, _extract_branch_susceptances_by_arc, _get_PTDF_A_diag so they precede the core in the load order.
  • virtual_ptdf_calculations.jl, virtual_modf_calculations.jl, virtual_lodf_calculations.jl: rewritten as wrappers over core. VirtualLODF keeps its own arc×arc axes/lookup and gains a C type parameter.
  • woodbury_kernel.jl: the duplicated outer wrappers collapse to one method on VirtualFactorCore; the wrappers forward to it.
  • New sharing constructors: VirtualMODF(vptdf, sys), VirtualLODF(vptdf), VirtualPTDF(core), VirtualMODF(core, sys).
  • dist_slack stays a PTDF/LODF wrapper concern (unused by MODF), never on the core.

Notes

  • The DC_vPTDF_Matrix alias is preserved (the wrapper keeps {Ax,L,K}).
  • test/test_virtual_shared_core.jl asserts a single shared factorization and numerical equivalence vs independently built objects. One white-box test that read getfield(vmodf, :PTDF_A_diag) was updated to read off the core.
  • Authored in an environment without Julia, so JuliaFormatter and the test suite are unverified locally — relying on CI here.

https://claude.ai/code/session_0195gQBv55bfXAmbUA3wmKQL


Generated by Claude Code

jd-lara and others added 5 commits May 31, 2026 15:00
avoid deep copy if the SolveCaches
VirtualPTDF, VirtualMODF, and VirtualLODF each rebuilt and stored the same
DC factorization (K/BA/A/ABA) and topology, duplicated the Woodbury outer
wrappers, and recomputed the same PTDF_A_diag / branch_susceptances_by_arc.

Introduce a private, unexported VirtualFactorCore that owns the factorization,
topology, solve scratch, solver lock, and the lazily-computed shared
PTDF_A_diag / branch_susceptances_by_arc. The three virtual matrices become
thin wrappers holding a `core` and forwarding to it via accessors and a
`getproperty` shim, so all existing public APIs (getindex, accessors,
get_post_modification_ptdf_row, ...) keep working unchanged.

A single core can now be shared across wrappers so the ABA matrix is
factorized once:
  vptdf = VirtualPTDF(Ybus(sys))
  vmodf = VirtualMODF(vptdf, sys)
  vlodf = VirtualLODF(vptdf)

- New src/virtual_factor_helpers.jl relocates _create_factorization,
  _solve_factorization, _extract_arc_susceptances,
  _extract_branch_susceptances_by_arc, _get_PTDF_A_diag so they precede the core.
- New src/virtual_factor_core.jl defines the struct, accessors, lazy getters,
  and the Ybus constructor.
- Woodbury outer wrappers collapse to one method on the core; the wrappers
  forward to it.
- dist_slack stays a PTDF/LODF wrapper concern (unused by MODF), never on core.
- Added test/test_virtual_shared_core.jl asserting one shared factorization and
  numerical equivalence vs independently built objects.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

Performance Results

Precompile Time

Main This Branch Delta
2.152 s 2.167 s +0.7%

Execution Time

Test Main This Branch Delta
matpower_ACTIVSg2000_sys-Build PTDF First 2.274 s 2.249 s -1.1%
matpower_ACTIVSg2000_sys-Build PTDF Second 101.0 ms 78.5 ms -22.3%
matpower_ACTIVSg2000_sys-Build Ybus First 11.8 ms 11.0 ms -7.5%
matpower_ACTIVSg2000_sys-Build Ybus Second 11.5 ms 10.5 ms -8.2%
matpower_ACTIVSg2000_sys-Build LODF First 152.5 ms 167.3 ms +9.7%
matpower_ACTIVSg2000_sys-Build LODF Second 635.4 ms 238.4 ms -62.5%
matpower_ACTIVSg2000_sys-Build VirtualMODF First 2.341 s 3.689 s +57.6%
matpower_ACTIVSg2000_sys-Build VirtualMODF Second 101.0 ms 955.5 ms +845.7%
matpower_ACTIVSg2000_sys-VirtualMODF Query 10 rows 372.4 ms 351.3 ms -5.7%
matpower_ACTIVSg2000_sys-Radial network reduction First 554.9 ms 531.7 ms -4.2%
matpower_ACTIVSg2000_sys-Radial network reduction Second 0.8 ms 0.7 ms -13.1%
matpower_ACTIVSg2000_sys-Degree two network reduction First 1.939 s 1.845 s -4.9%
matpower_ACTIVSg2000_sys-Degree two network reduction Second 1.1 ms 0.9 ms -14.8%
Base_Eastern_Interconnect_515GW-Build Ybus First 1.16 s 847.0 ms -27.0%
Base_Eastern_Interconnect_515GW-Build Ybus Second 643.0 ms 877.3 ms +36.4%
Base_Eastern_Interconnect_515GW-Radial network reduction First 44.2 ms 40.2 ms -9.2%
Base_Eastern_Interconnect_515GW-Radial network reduction Second 44.0 ms 37.1 ms -15.6%
Base_Eastern_Interconnect_515GW-Degree two network reduction First 399.2 ms 394.1 ms -1.3%
Base_Eastern_Interconnect_515GW-Degree two network reduction Second 47.8 ms 43.1 ms -9.8%

Confine getfield to the field-getter definitions and the getproperty
hooks. Introduce get_core (and get_cache/get_dist_slack/... companions)
on VirtualPTDF/VirtualMODF/VirtualLODF and read wrapper fields through
these accessors everywhere else, including the shared-core tests.
@jd-lara jd-lara changed the base branch from main to psy6 June 2, 2026 13:57
@jd-lara jd-lara merged commit e62eca4 into psy6 Jun 2, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants