Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
PowerCoreOpenAPIModels = "b7b40286-e793-417d-a9a0-b1583e4da1cb"
PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
Expand All @@ -38,6 +39,7 @@ JuMP = "^1.28"
LinearAlgebra = "1"
Logging = "1"
MathOptInterface = "1"
PowerCoreOpenAPIModels = "0.1"
PrettyTables = "3.1"
Random = "^1.10"
Serialization = "1"
Expand All @@ -47,3 +49,6 @@ julia = "^1.10"

[sources]
InfrastructureSystems = {url = "https://github.com/Sienna-Platform/InfrastructureSystems.jl", rev = "IS4"}
# IS4's own [sources] pin for this is ignored once IS is a dependency rather than the root
# project; IOM needs its own pin so Pkg can resolve IS4's unregistered OpenAPI dep.
PowerCoreOpenAPIModels = {url = "https://github.com/Sienna-Platform/PowerOpenAPIModels.git", rev = "main", subdir = "PowerCoreOpenAPIModels.jl"}
6 changes: 5 additions & 1 deletion src/InfrastructureOptimizationModels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ export InitialCondition
# Network Relevant Exports
export AbstractNetworkModel
export NetworkModel
export get_network_matrix, get_contingency_matrix, get_reduce_radial_branches
export get_network_matrix, get_contingency_matrix
export get_network_source, get_reduction_exceptions, get_network_data, set_network_data!
export get_outages
export get_duals, get_reference_buses, get_subnetworks, get_bus_area_map
export get_evaluations, has_subnetworks, get_subsystem
Expand All @@ -178,6 +179,9 @@ export supports_outages
export validate_network_model
export AbstractBranchReductionTracker
export set_reduced_branch_tracker!
export AbstractNetworkSource
export AbstractNetworkData
export DefaultNetworkSource
# Note: Concrete network model types (PTDFPowerModel, CopperPlatePowerModel, etc.)
# and the branch-reduction tracker machinery are defined in PowerOperationsModels, not IOM.

Expand Down
107 changes: 66 additions & 41 deletions src/core/network_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,27 @@ via [`set_reduced_branch_tracker!`](@ref).
"""
abstract type AbstractBranchReductionTracker end

"""
Abstract anchor for the declaration of which network a `NetworkModel` is built on and
how it is reduced. Concrete sources (a reduction specification, a prebuilt matrix, a
prebuilt factorization core) live in the matrix-aware downstream package (POM), so
IOM carries no dependency on any matrix implementation.
"""
abstract type AbstractNetworkSource end

"""
Abstract anchor for the network artifacts a build derives from an
[`AbstractNetworkSource`](@ref): the reduction data plus whichever matrices the
network formulation needs. Concrete containers live in POM.
"""
abstract type AbstractNetworkData end

"""
Source used when a `NetworkModel` is constructed without an explicit one: build the
network from the system with no reductions applied.
"""
struct DefaultNetworkSource <: AbstractNetworkSource end

"Abstract supertype for network model formulations; neutral anchor for NetworkModel{T}."
abstract type AbstractNetworkModel <: IS.Optimization.AbstractInfrastructureModel end

Expand All @@ -32,52 +53,44 @@ Establishes the NetworkModel for a given AC network formulation type.
# Accepted keyword arguments
- `use_slacks::Bool` = false
Adds slack buses to the network modeling.
- `network_matrix::Union{AbstractInfrastructureNetworkMatrix, Nothing}` = nothing
Network matrix (e.g. PTDF/VirtualPTDF produced by PowerNetworkMatrices; optional).
- `contingency_matrix::Union{AbstractInfrastructureNetworkMatrix, Nothing}` = nothing
Contingency matrix (e.g. VirtualMODF) for security-constrained models (N-k contingencies).
If `nothing` and the template includes a security-constrained branch
formulation, the matrix is constructed from the system during
`instantiate_network_model!` (same pattern as PTDF).
- `reduce_radial_branches::Bool` = false
Enable radial branch reduction when building network matrices.
- `reduce_degree_two_branches::Bool` = false
Enable degree-two branch reduction when building network matrices.
- `subnetworks::Dict{Int, Set{Int}}` = Dict()
Optional mapping of reference bus → set of mapped buses. If not provided,
subnetworks are inferred from the network matrix or discovered from the system.
- `network_source::AbstractNetworkSource` = `DefaultNetworkSource()`
Declares which network the model is built on and how it is reduced. The default
builds it from the system with no reductions. Concrete sources (a reduction
specification, a prebuilt matrix, a prebuilt factorization core) live in the
matrix-aware downstream package.
- `reduction_exceptions::Vector{Int}` = `Int[]`
Bus numbers the reduction must not eliminate, on top of those the template
itself pins.
- `duals::Vector{DataType}` = Vector{DataType}()
Constraint types for which duals should be recorded.
- `evaluations::EvaluationContainer`
External evaluators (e.g. power-flow) keyed by concrete evaluator type.
Default is an empty container — no evaluator runs.

# Notes
- `modeled_branch_types` and `reduced_branch_tracker` are internal fields managed by the model.
- `network_data` holds every matrix and the reduction data derived from
`network_source` during `instantiate_network_model!`; it is `nothing` before then.
- `subnetworks`, `modeled_branch_types` and `reduced_branch_tracker` are internal
fields managed by the model.
- `subsystem` can be set after construction via `set_subsystem!(model, id)`.
- Network and contingency matrix inputs are validated against the requested reduction flags and
may raise a ConflictingInputsError if they are inconsistent with
`reduce_radial_branches` or `reduce_degree_two_branches`.

# Examples (concrete types like PTDFPowerModel, CopperPlatePowerModel are defined in PowerSimulations)
# ptdf = PowerNetworkMatrices.VirtualPTDF(system)
# ec = EvaluationContainer()
# add_evaluator!(ec, PFS.PowerFlowEvaluationModel, PFS.PowerFlowEvaluationModel())
# nw = NetworkModel(PTDFPowerModel; network_matrix = ptdf, reduce_radial_branches = true,
# nw = NetworkModel(PTDFPowerModel;
# network_source = NetworkReductionSpec(RadialReduction()),
# evaluations = ec)
#
# nw2 = NetworkModel(CopperPlatePowerModel; subnetworks = Dict(1 => Set([1,2,3])))
# nw2 = NetworkModel(CopperPlatePowerModel)
"""
mutable struct NetworkModel{T <: AbstractNetworkModel}
use_slacks::Bool
network_matrix::Union{Nothing, AbstractInfrastructureNetworkMatrix}
contingency_matrix::Union{Nothing, AbstractInfrastructureNetworkMatrix}
network_source::AbstractNetworkSource
reduction_exceptions::Vector{Int}
subnetworks::Dict{Int, Set{Int}}
bus_area_map::Dict{IS.InfrastructureSystemsComponent, Int}
duals::Vector{DataType}
network_reduction::Union{Nothing, AbstractInfrastructureNetworkReductionData}
reduce_radial_branches::Bool
reduce_degree_two_branches::Bool
network_data::Union{Nothing, AbstractNetworkData}
evaluations::EvaluationContainer
subsystem::Union{Nothing, String}
hvdc_network_model::Union{Nothing, AbstractHVDCNetworkModel}
Expand All @@ -87,28 +100,23 @@ mutable struct NetworkModel{T <: AbstractNetworkModel}
function NetworkModel(
::Type{T};
use_slacks = false,
network_matrix = nothing,
contingency_matrix = nothing,
reduce_radial_branches = false,
reduce_degree_two_branches = false,
subnetworks = Dict{Int, Set{Int}}(),
network_source = DefaultNetworkSource(),
reduction_exceptions = Int[],
duals = Vector{DataType}(),
evaluations = EvaluationContainer(),
hvdc_network_model = nothing,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing the hvdc_network_model is domain specific and doesn't belong in IOM

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is TBD to be removed

) where {T <: AbstractNetworkModel}
_check_network_formulation(T)
new{T}(
use_slacks,
network_matrix,
contingency_matrix,
subnetworks,
network_source,
reduction_exceptions,
Dict{Int, Set{Int}}(),
Dict{IS.InfrastructureSystemsComponent, Int}(),
duals,
# Populated by the network-matrix-aware instantiation code (POM); IOM
# holds it behind the IS abstraction so it carries no PNM dependency.
# holds it behind an abstract type so it carries no PNM dependency.
nothing,
reduce_radial_branches,
reduce_degree_two_branches,
evaluations,
nothing,
hvdc_network_model,
Expand All @@ -119,11 +127,23 @@ mutable struct NetworkModel{T <: AbstractNetworkModel}
end

get_use_slacks(m::NetworkModel) = m.use_slacks
get_network_matrix(m::NetworkModel) = m.network_matrix
get_contingency_matrix(m::NetworkModel) = m.contingency_matrix
get_reduce_radial_branches(m::NetworkModel) = m.reduce_radial_branches
get_network_reduction(m::NetworkModel) = m.network_reduction
get_network_source(m::NetworkModel) = m.network_source
get_reduction_exceptions(m::NetworkModel) = m.reduction_exceptions
get_network_data(m::NetworkModel) = m.network_data
get_duals(m::NetworkModel) = m.duals

"""
The network matrix derived during instantiation. Implemented in the matrix-aware
downstream package, which owns the concrete `AbstractNetworkData`.
"""
function get_network_matrix end

"""The contingency matrix derived during instantiation. Implemented downstream."""
function get_contingency_matrix end

"""The network reduction derived during instantiation. Implemented downstream."""
function get_network_reduction end

Comment on lines +135 to +146

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of defining these in IOM and implementing them downstream in POM? I'm guessing there is a reason but want to understand.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is because these are methods defined for network model. I think that NetworkModel still has pending work to see how it would generalize to infrastructure networks.

get_network_formulation(::NetworkModel{T}) where {T} = T
get_reduced_branch_tracker(m::NetworkModel) = m.reduced_branch_tracker
get_reference_buses(m::NetworkModel{T}) where {T <: AbstractNetworkModel} =
Expand All @@ -143,6 +163,11 @@ function set_reduced_branch_tracker!(m::NetworkModel, val::AbstractBranchReducti
return
end

function set_network_data!(m::NetworkModel, val::Union{Nothing, AbstractNetworkData})
m.network_data = val
return
end

function add_dual!(model::NetworkModel, dual)
dual in model.duals && error("dual = $dual is already stored")
push!(model.duals, dual)
Expand Down
25 changes: 13 additions & 12 deletions src/operation/problem_template.jl
Original file line number Diff line number Diff line change
Expand Up @@ -135,27 +135,28 @@ function finalize_template!(template::AbstractProblemTemplate, args...)
)
end

# Deep-copy a template while sharing the network model's PNM matrices by reference:
# their solver caches hold raw factorization handles and deliberately error on deepcopy
# (PNM #312). The matrices are read-only inputs, so sharing is safe.
# Deep-copy a template while sharing the network source and derived data by reference:
# both can hold PNM matrices whose solver caches carry raw factorization handles and
# deliberately error on deepcopy (PNM #312). Sharing is safe because instantiation
# replaces the copy's `network_data` outright rather than mutating the original's.
function _deepcopy_template(template::AbstractProblemTemplate)
network_model = get_network_model(template)
network_model === nothing && return deepcopy(template)
ptdf = network_model.network_matrix
modf = network_model.contingency_matrix
network_model.network_matrix = nothing
network_model.contingency_matrix = nothing
# Restore the input template's matrices even if `deepcopy` throws, so a
source = network_model.network_source
network_data = network_model.network_data
network_model.network_source = DefaultNetworkSource()
network_model.network_data = nothing
# Restore the input template's source and data even if `deepcopy` throws, so a
# failed copy doesn't leave the caller's template stripped.
template_ = try
deepcopy(template)
finally
network_model.network_matrix = ptdf
network_model.contingency_matrix = modf
network_model.network_source = source
network_model.network_data = network_data
end
copied_network_model = get_network_model(template_)
copied_network_model.network_matrix = ptdf
copied_network_model.contingency_matrix = modf
copied_network_model.network_source = source
copied_network_model.network_data = network_data
return template_
end

Expand Down
2 changes: 1 addition & 1 deletion src/utils/print_pt_v3.jl
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ function _show_method(io::IO, network_model::NetworkModel, backend::Symbol; kwar
table = [
"Network Model" string(get_network_formulation(network_model))
"Slacks" get_use_slacks(network_model)
"Network Matrix" !isnothing(get_network_matrix(network_model))
"Network Data" !isnothing(get_network_data(network_model))
"Duals" join(string.(get_duals(network_model)), " ")
]

Expand Down
4 changes: 4 additions & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
PowerCoreOpenAPIModels = "b7b40286-e793-417d-a9a0-b1583e4da1cb"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
SCS = "c946c3f1-0d1f-5ce8-9dea-7daa1f7e2d13"
Expand All @@ -31,6 +32,9 @@ UnoSolver = "1baa60ac-02f7-4b39-a7a8-2f4f58486b05"
[sources]
InfrastructureSystems = {url = "https://github.com/Sienna-Platform/InfrastructureSystems.jl", rev = "IS4"}
InfrastructureOptimizationModels = {path = ".."}
# IS4's own [sources] pin for this is ignored once IS is a dependency rather than the root
# project; the test env needs its own pin so Pkg can resolve IS4's unregistered OpenAPI dep.
PowerCoreOpenAPIModels = {url = "https://github.com/Sienna-Platform/PowerOpenAPIModels.git", rev = "main", subdir = "PowerCoreOpenAPIModels.jl"}

[compat]
HiGHS = "1"
Expand Down
44 changes: 24 additions & 20 deletions test/test_network_model.jl
Original file line number Diff line number Diff line change
@@ -1,37 +1,41 @@
"""
Unit tests for NetworkModel with the IS network-matrix abstractions. IOM holds
matrices, reduction data, and the branch-reduction tracker behind abstract types
with no PowerNetworkMatrices dependency; these mocks stand in for the
implementing package (POM/PNM).
Unit tests for NetworkModel's neutral network anchors. IOM holds the network source,
the derived network data, and the branch-reduction tracker behind abstract types with
no PowerNetworkMatrices dependency; these mocks stand in for the implementing package
(POM/PNM).
"""

struct MockNetworkMatrix <: IOM.AbstractInfrastructureNetworkMatrix{Float64}
data::Matrix{Float64}
end
Base.size(m::MockNetworkMatrix) = size(m.data)
Base.getindex(m::MockNetworkMatrix, i::Int, j::Int) = m.data[i, j]
struct MockNetworkSource <: IOM.AbstractNetworkSource end

struct MockReductionData <: IOM.AbstractInfrastructureNetworkReductionData end
struct MockNetworkData <: IOM.AbstractNetworkData end

struct MockReductionTracker <: IOM.AbstractBranchReductionTracker end

@testset "NetworkModel with abstract network matrices" begin
matrix = MockNetworkMatrix([1.0 0.0; 0.0 1.0])
@testset "NetworkModel with abstract network source and data" begin
source = MockNetworkSource()
nw = IOM.NetworkModel(
TestPowerModel;
network_matrix = matrix,
contingency_matrix = matrix,
network_source = source,
reduction_exceptions = [3, 7],
)
@test IOM.get_network_matrix(nw) === matrix
@test IOM.get_contingency_matrix(nw) === matrix
# Reduction data and tracker are populated by the matrix-aware downstream package.
@test IOM.get_network_reduction(nw) === nothing
@test IOM.get_network_source(nw) === source
@test IOM.get_reduction_exceptions(nw) == [3, 7]
# Network data and tracker are populated by the matrix-aware downstream package.
@test IOM.get_network_data(nw) === nothing
@test IOM.get_reduced_branch_tracker(nw) === nothing

nw.network_reduction = MockReductionData()
@test IOM.get_network_reduction(nw) isa MockReductionData
data = MockNetworkData()
IOM.set_network_data!(nw, data)
@test IOM.get_network_data(nw) === data

tracker = MockReductionTracker()
IOM.set_reduced_branch_tracker!(nw, tracker)
@test IOM.get_reduced_branch_tracker(nw) === tracker
end

@testset "NetworkModel defaults to the neutral source" begin
nw = IOM.NetworkModel(TestPowerModel)
@test IOM.get_network_source(nw) === IOM.DefaultNetworkSource()
@test isempty(IOM.get_reduction_exceptions(nw))
@test isempty(IOM.get_subnetworks(nw))
end
Loading