add monitored components to exceptions automatically#309
Conversation
There was a problem hiding this comment.
Pull request overview
This PR ensures VirtualMODF remains reduction-consistent by automatically protecting (i.e., retaining through network reductions) the buses needed to keep all outaged and outage-declared monitored transmission components expressible as arcs, preventing incorrect ABA/Woodbury behavior and confusing query failures.
Changes:
- Add logic to collect “protected buses” from outaged components and their
monitored_components, and inject them into requested network reductions. - Improve failure/diagnostics by warning when a transmission outage is dropped by reductions and by providing a clearer error when querying a reduced-away monitored arc tuple.
- Add a comprehensive regression test suite covering protection behavior across Radial/DegreeTwo/Ward reductions, monitored components, and error/warn paths.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
src/modf_reduction_consistency.jl |
New helpers to compute protected buses and augment reductions accordingly. |
src/virtual_modf_calculations.jl |
Wire protection into VirtualMODF construction; add warning for dropped transmission outages; add clearer reduced-arc tuple error. |
src/PowerNetworkMatrices.jl |
Include the new consistency helper file in module load order. |
test/test_modf_reduction_consistency.jl |
New end-to-end and helper-level tests for reduction/outage/monitoring consistency. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - `network_reduction_data::NetworkReductionData`: | ||
| Network reduction mappings for branch resolution. | ||
| Network reduction mappings for branch resolution. Its `irreducible_buses` | ||
| set is the authoritative record of buses retained from reduction, | ||
| including those protected so every outaged and monitored component stays | ||
| expressible as an arc (the MODF "exception list"). |
| isempty(mod.arc_modifications) || return | ||
| transmission = | ||
| PSY.get_associated_components(sys, outage; component_type = PSY.ACTransmission) | ||
| isempty(collect(transmission)) && return | ||
| @warn "Outage (label=$(mod.label)) references transmission components but " * |
| if isempty(network_reductions) | ||
| applied_reductions = network_reductions | ||
| else | ||
| protected_buses = _collect_protected_buses(sys) | ||
| applied_reductions = | ||
| _adjust_reductions_for_protection(network_reductions, protected_buses) | ||
| end |
| PSY.add_supplemental_attribute!( | ||
| sys, | ||
| outaged, | ||
| _fixed_outage(; monitored = [monitored]), |
| PSY.add_supplemental_attribute!( | ||
| sys, | ||
| outaged, | ||
| _fixed_outage(; monitored = [monitored]), |
Performance ResultsPrecompile Time
Execution Time
|
| - `network_reduction_data::NetworkReductionData`: | ||
| Network reduction mappings for branch resolution. | ||
| Network reduction mappings for branch resolution. Its `irreducible_buses` | ||
| set records the buses retained from reduction, including those protected | ||
| so outaged and monitored components stay queryable (the "exception list"). |
| # Ward retains `study_buses`; a protected external bus must join the study area | ||
| # so its incident branch is not equivalenced away. | ||
| function _augment_reduction(reduction::WardReduction, protected::Set{Int}) | ||
| isempty(protected) && return reduction | ||
| return WardReduction(_merge_irreducible(reduction.study_buses, protected)) | ||
| end | ||
|
|
||
| """ | ||
| _adjust_reductions_for_protection(reductions, protected) -> Vector{NetworkReduction} | ||
|
|
||
| Return a new reduction vector in which each reduction's protected-bus set has | ||
| been extended with `protected`. | ||
| """ | ||
| function _adjust_reductions_for_protection( | ||
| reductions::Vector{NetworkReduction}, | ||
| protected::Set{Int}, | ||
| ) | ||
| return NetworkReduction[_augment_reduction(r, protected) for r in reductions] | ||
| end |
| _fixed_outage(; monitored = Base.UUID[]) = | ||
| PSY.FixedForcedOutage(; outage_status = 0.0, monitored_components = monitored) |
|
@m-bossart this PR surfaced a bug in PFs reporting code fixed here Sienna-Platform/PowerFlows.jl@3d7a71c |
| PSY.add_supplemental_attribute!( | ||
| sys, | ||
| outaged, | ||
| _fixed_outage(; monitored = [monitored]), |
| k_j = j_start + offset + idx | ||
| r = rows[k_j] | ||
| v_j = vals[k_j] | ||
| # Snapshot column j up front. Structural inserts into column i shift the CSC backing |
There was a problem hiding this comment.
I'm not sure I fully understand the tradeoff for these two implementations.
There was a problem hiding this comment.
The old version tracked CSC backing-store offsets assuming inserts into column i shift the storage in a fixed direction this is only true when i < j. Once we added the zero-impedance merges where the survivor index i > removed index j, that bookkeeping dropped a mutual term, producing an asymmetric Ybus. This is where the PowerFlow tests started to fail
| # Radial uses only the user set; no system-derived complement. | ||
| user_irreducible = | ||
| get_user_irreducible_buses(get_reductions(get_network_reduction_data(A))) | ||
| irreducible_positions = Set([A.lookup[2][x] for x in user_irreducible]) |
| @@ -271,16 +280,24 @@ function VirtualMODF( | |||
| tol::Float64 = eps(), | |||
| max_cache_size::Int = MAX_CACHE_SIZE_MiB, | |||
| network_reductions::Vector{NetworkReduction} = NetworkReduction[], | |||
| irreducible_buses::Set{Int} = Set{Int}(), | |||
| automatically_register_outages::Bool = true, | |||
| kwargs..., | |||
| function _arc_conecting_two_areas(arc::PSY.Arc) | ||
| from_bus = PSY.get_from(arc) | ||
| to_bus = PSY.get_to(arc) | ||
| area_from = IS.get_uuid(PSY.get_area(from_bus)) | ||
| area_to = IS.get_uuid(PSY.get_area(to_bus)) | ||
| return area_from != area_to | ||
| end | ||
|
|
||
| _arc_conecting_two_areas(br::PSY.ACTransmission) = | ||
| _arc_conecting_two_areas(PSY.get_arc(br)) |
| function _arc_conecting_two_areas(br::PSY.ThreeWindingTransformer) | ||
| # For the 3WT all the 4 buses are kept if any of the 3 arcs connect two areas | ||
| arcs = [ | ||
| PSY.get_primary_star_arc(br), | ||
| PSY.get_secondary_star_arc(br), | ||
| PSY.get_tertiary_star_arc(br), | ||
| ] | ||
| for arc in arcs | ||
| if _arc_conecting_two_areas(arc) | ||
| return true | ||
| end | ||
| end | ||
| return false |
| (PSY.get_available(br) && _arc_conecting_two_areas(br)) || continue | ||
| _add_arc_buses!(buses, br) |
| # The monitored set is carried ON the outage as device UUIDs. | ||
| PSY.add_supplemental_attribute!( | ||
| sys, | ||
| outaged, | ||
| _fixed_outage(; monitored = [monitored]), | ||
| ) |
| PSY.add_supplemental_attribute!( | ||
| sys, | ||
| outaged, | ||
| _fixed_outage(; monitored = [monitored]), | ||
| ) |
There was a problem hiding this comment.
all of these are false positive
This PR makes sure that if reductions are applied then the outages monitored components buses are automatically added to the exceptions list to prevent incorrect ABA calculations