diff --git a/Project.toml b/Project.toml index 67e3263..dd1eb25 100644 --- a/Project.toml +++ b/Project.toml @@ -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" @@ -38,6 +39,7 @@ JuMP = "^1.28" LinearAlgebra = "1" Logging = "1" MathOptInterface = "1" +PowerCoreOpenAPIModels = "0.1" PrettyTables = "3.1" Random = "^1.10" Serialization = "1" @@ -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"} diff --git a/src/InfrastructureOptimizationModels.jl b/src/InfrastructureOptimizationModels.jl index aa127c3..2cc3039 100644 --- a/src/InfrastructureOptimizationModels.jl +++ b/src/InfrastructureOptimizationModels.jl @@ -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 @@ -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. diff --git a/src/core/network_model.jl b/src/core/network_model.jl index d6493bb..3818fcc 100644 --- a/src/core/network_model.jl +++ b/src/core/network_model.jl @@ -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 @@ -32,20 +53,14 @@ 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` @@ -53,31 +68,29 @@ Establishes the NetworkModel for a given AC network formulation 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} @@ -87,11 +100,8 @@ 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, @@ -99,16 +109,14 @@ mutable struct NetworkModel{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, @@ -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 + 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} = @@ -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) diff --git a/src/operation/problem_template.jl b/src/operation/problem_template.jl index 75b5932..8b8039e 100644 --- a/src/operation/problem_template.jl +++ b/src/operation/problem_template.jl @@ -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 diff --git a/src/utils/print_pt_v3.jl b/src/utils/print_pt_v3.jl index cd15752..045905d 100644 --- a/src/utils/print_pt_v3.jl +++ b/src/utils/print_pt_v3.jl @@ -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)), " ") ] diff --git a/test/Project.toml b/test/Project.toml index 3382525..2450281 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -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" @@ -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" diff --git a/test/test_network_model.jl b/test/test_network_model.jl index e499f11..7726acf 100644 --- a/test/test_network_model.jl +++ b/test/test_network_model.jl @@ -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