Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ PowerFlows = "94fada2c-fd9a-4e89-8d82-81405f5cb4f6"
# than PSY's local checkout expects (TimeSeriesKey UnionAll mismatch on load).
# Revert to the git rev pin once PSY's local changes land on IS4.
InfrastructureSystems = {rev = "IS4", url = "https://github.com/Sienna-Platform/InfrastructureSystems.jl"}
InfrastructureOptimizationModels = {rev = "jd/pwl_width_refs", url = "https://github.com/Sienna-Platform/InfrastructureOptimizationModels.jl"}
InfrastructureOptimizationModels = {rev = "main", url = "https://github.com/Sienna-Platform/InfrastructureOptimizationModels.jl"}
# LOCAL PATH CO-DEV PIN (temporary): the variable -> variable_operation_cost cost-field
# rename on PSY's generated cost types is uncommitted on disk, not on the psy6 branch yet.
# Revert to the git rev pin once pushed/merged.
Expand Down
21 changes: 14 additions & 7 deletions src/network_models/instantiate_network_model.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""
Concrete implementations of `instantiate_network_model!` for specific network formulations.

These methods extend the generic dispatch from IOM's `operation_model_interface.jl`, which
calls `instantiate_network_model!(network_model, branch_models, number_of_steps, sys)`.
These methods extend the generic dispatch from IOM's `optimization_model_interface.jl`, which
calls
`instantiate_network_model!(network_model, branch_models, service_models, number_of_steps, sys)`.
Each method here handles the formulation-specific setup: computing PTDF/MODF matrices,
discovering subnetworks, applying network reductions, etc.
"""
Expand Down Expand Up @@ -285,8 +286,11 @@ function _validate_prebuilt_exceptions(
throw(
IS.ConflictingInputsError(
"The prebuilt network source eliminated buses $(sort!(collect(dropped))), which \
the template pins as reduction exceptions (outage-monitored components or \
time-varying branch ratings). Rebuild the matrix with these buses passed as \
the model pins as reduction exceptions: the NetworkModel's own \
`reduction_exceptions`, plus the buses of DC converter terminals, branches with \
rating time series, outaged and monitored components, `model_all_branches` \
monitored lines, controlled transformer circuits and their regulated buses, and \
transmission interface branches. Rebuild the matrix with these buses passed as \
`irreducible_buses`, or pass a `SystemNetworkSource` so the build derives the \
reduction itself.",
),
Expand Down Expand Up @@ -401,11 +405,12 @@ end
function IOM.instantiate_network_model!(
model::NetworkModel{T},
branch_models::BranchModelContainer,
service_models::ServicesModelContainer,
number_of_steps::Int,
sys::PSY.System,
) where {T <: AbstractNetworkModel}
_validate_network_and_branches(model, branch_models, sys)
exceptions = _collect_reduction_exceptions(sys, model, branch_models)
exceptions = _collect_reduction_exceptions(sys, model, branch_models, service_models)
ybus = _reduced_ybus!(model, sys, exceptions)
IOM.set_network_data!(
model,
Expand All @@ -422,11 +427,12 @@ end
function IOM.instantiate_network_model!(
model::NetworkModel{DCPNetworkModel},
branch_models::BranchModelContainer,
service_models::ServicesModelContainer,
number_of_steps::Int,
sys::PSY.System,
)
_validate_network_and_branches(model, branch_models, sys)
exceptions = _collect_reduction_exceptions(sys, model, branch_models)
exceptions = _collect_reduction_exceptions(sys, model, branch_models, service_models)
ybus = _reduced_ybus!(model, sys, exceptions)
catalog = _build_catalog(ybus, branch_models)
if IOM._template_has_outage_aware_branch(branch_models)
Expand All @@ -453,11 +459,12 @@ end
function IOM.instantiate_network_model!(
model::NetworkModel{T},
branch_models::BranchModelContainer,
service_models::ServicesModelContainer,
number_of_steps::Int,
sys::PSY.System,
) where {T <: AbstractPTDFNetworkModel}
_validate_network_and_branches(model, branch_models, sys)
exceptions = _collect_reduction_exceptions(sys, model, branch_models)
exceptions = _collect_reduction_exceptions(sys, model, branch_models, service_models)
IOM.set_network_data!(
model,
_ptdf_network_data(
Expand Down
21 changes: 21 additions & 0 deletions src/network_models/reduction_exceptions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ function _collect_reduction_exceptions(
sys::PSY.System,
network_model::NetworkModel,
branch_models::BranchModelContainer,
service_models::ServicesModelContainer,
)
@debug "Collecting reduction exceptions" _group =
IOM.LOG_GROUP_NETWORK_CONSTRUCTION
Expand All @@ -61,9 +62,29 @@ function _collect_reduction_exceptions(
_pin_model_all_branches!(buses, m)
_pin_transformer_controls!(buses, m, sys, network_model)
end
for m in values(service_models)
_pin_interface_branch_buses!(buses, m)
end
return collect(buses)
end

# A branch in a modeled TransmissionInterface pins both its endpoints: the interface's flow
# expression reads the branch's own flow, and a reduction that merged the branch into a
# series or parallel equivalent (or eliminated it as radial) would leave that flow with no
# term to read. Reads the contributing devices `finalize_template!` populated, so only the
# interfaces the template models are pinned, and only their branch-typed contributors.
function _pin_interface_branch_buses!(
buses::Set{Int},
m::ServiceModel{<:PSY.TransmissionInterface},
)
for branch in get_contributing_devices(m)
_push_component_buses!(buses, branch)
end
return
end

_pin_interface_branch_buses!(::Set{Int}, ::ServiceModel) = nothing

# A converter's AC terminal must survive the reduction. Merging one away drops the
# converter from the model without a word, so this is keyed on the system rather than on a
# DeviceModel — the exposure exists whether or not the template happens to model the
Expand Down
97 changes: 97 additions & 0 deletions src/operation/template_validation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ function validate_template_impl!(model::IOM.AbstractOptimizationModel)
for k in branch_keys_to_delete
delete!(template.branches, k)
end
_check_interface_branches(template, system, network_model)
_check_security_constrained_three_winding_transformer(template.branches)
_check_security_constrained_network(template.branches, network_model)
_check_security_constrained_phase_control(template.branches, network_model)
Expand All @@ -121,6 +122,102 @@ function validate_template_impl!(model::IOM.AbstractOptimizationModel)
return
end

#################################################################################
# Transmission interface contributors
#################################################################################

# Whether the network formulation builds a flow for this interface contributor, so that the
# interface's flow expression reads it. Aggregated formulations carry no AC branch flow (the
# AreaBalance interface constructor warns that it ignores them); an AreaInterchange is modeled
# wherever its branch model is.
_interface_contributor_has_flow(
::PSY.ACTransmission,
::Type{N},
) where {N <: AbstractNetworkModel} = branches_modeled(N)
_interface_contributor_has_flow(::PSY.AreaInterchange, ::Type{<:AbstractNetworkModel}) =
true
_interface_contributor_has_flow(::PSY.Device, ::Type{<:AbstractNetworkModel}) = false

"""
Reject a template whose transmission interfaces include a branch the model builds no flow
for: either the branch's type has no branch model in the template, or that branch model's
`filter_function` (or subsystem) excludes the branch. Either way the interface's flow
expression would silently omit the branch, so the template is rejected and the user must
make the filter and the interface definitions consistent.
"""
function _check_interface_branches(
template::PowerOperationsProblemTemplate,
sys::PSY.System,
::NetworkModel{N},
) where {N <: AbstractNetworkModel}
problems = String[]
modeled_names = Dict{DataType, Set{String}}()
for service_model in values(get_service_models(template))
get_component_type(service_model) <: PSY.TransmissionInterface || continue
for interface in get_available_components(service_model, sys)
_interface_branch_problems!(
problems,
modeled_names,
template,
sys,
interface,
N,
)
end
end
isempty(problems) && return
throw(IS.ConflictingInputsError(join(problems, "\n")))
end

function _interface_branch_problems!(
problems::Vector{String},
modeled_names::Dict{DataType, Set{String}},
template::PowerOperationsProblemTemplate,
sys::PSY.System,
interface::PSY.TransmissionInterface,
::Type{N},
) where {N <: AbstractNetworkModel}
unmodeled = Dict{DataType, Vector{String}}()
excluded = Dict{DataType, Vector{String}}()
for branch in PSY.get_contributing_devices(sys, interface)
PSY.get_available(branch) || continue
_interface_contributor_has_flow(branch, N) || continue
T = typeof(branch)
branch_model = get_model(template, T)
name = PSY.get_name(branch)
if branch_model === nothing
push!(get!(Vector{String}, unmodeled, T), name)
continue
end
names = get!(modeled_names, T) do
Set{String}(PSY.get_name(b) for b in get_device_cache(branch_model))
end
name ∈ names || push!(get!(Vector{String}, excluded, T), name)
end
interface_name = PSY.get_name(interface)
for (T, names) in unmodeled
push!(
problems,
"TransmissionInterface \"$(interface_name)\" includes $(T) branches \
$(sort!(names)) but the template has no branch model for $(T); their flow would \
be omitted from the interface. Add a branch model for $(T), remove the \
TransmissionInterface service model from the template, or remove the branches \
from the interface in the system data.",
)
end
for (T, names) in excluded
push!(
problems,
"TransmissionInterface \"$(interface_name)\" includes $(T) branches \
$(sort!(names)) that the template's $(T) branch model excludes through its \
filter_function or subsystem; their flow would be omitted from the interface. \
Change the filter_function to admit every branch in the interface, or remove \
the TransmissionInterface service model from the template.",
)
end
return
end

#################################################################################
# Security-constrained branch validation and outage population
#################################################################################
Expand Down
2 changes: 1 addition & 1 deletion test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ PowerOperationsModels = {path = ".."}
# than PSY's local checkout expects (TimeSeriesKey UnionAll mismatch on load).
# Revert to the git rev pin once PSY's local changes land on IS4.
InfrastructureSystems = {rev = "IS4", url = "https://github.com/Sienna-Platform/InfrastructureSystems.jl"}
InfrastructureOptimizationModels = {rev = "jd/pwl_width_refs", url = "https://github.com/Sienna-Platform/InfrastructureOptimizationModels.jl"}
InfrastructureOptimizationModels = {rev = "main", url = "https://github.com/Sienna-Platform/InfrastructureOptimizationModels.jl"}
# LOCAL PATH CO-DEV PIN (temporary): the variable -> variable_operation_cost cost-field
# rename on PSY's generated cost types is uncommitted on disk, not on the psy6 branch yet.
# Revert to the git rev pin once pushed/merged.
Expand Down
65 changes: 65 additions & 0 deletions test/test_native_network_reductions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,7 @@ end
sys,
get_network_model(get_template(model)),
branch_models,
get_service_models(get_template(model)),
)
# "4-5-i_1" carries the rating time series; its endpoint buses (4, 5) must be
# pinned so the reduction can't merge them away.
Expand All @@ -793,6 +794,70 @@ end
@test length(exceptions) == 2
end

@testset "Interface branches are pinned from the build's reduction" begin
# "1-8-i_1" is the radial branch to leaf bus 8, which RadialReduction folds into bus 1
# unless something pins it. An interface over that branch reads its flow, so modeling
# the interface must keep the branch (and bus 8) in the reduced network.
function _build_with_interface(model_interface::Bool)
sys = _case11_with_forecast()
interface = TransmissionInterface(;
name = "leaf_interface",
available = true,
active_power_flow_limits = (min = -100.0, max = 100.0),
)
add_service!(sys, interface, [get_component(Line, sys, "1-8-i_1")])
net = NetworkModel(
POM.DCPNetworkModel;
network_source = SystemNetworkSource(PNM.RadialReduction()),
)
template = get_thermal_dispatch_template_network(net)
if model_interface
set_service_model!(
template,
ServiceModel(TransmissionInterface, ConstantMaxInterfaceFlow),
)
end
model = DecisionModel(template, sys; optimizer = HiGHS_optimizer)
@test build!(
model;
output_dir = mktempdir(; cleanup = true),
console_level = Logging.Error,
) == IOM.ModelBuildStatus.BUILT
return model
end

unpinned = _build_with_interface(false)
unpinned_network = get_network_model(get_template(unpinned))
@test !(8 in keys(PNM.get_bus_reduction_map(get_network_reduction(unpinned_network))))

pinned = _build_with_interface(true)
pinned_template = get_template(pinned)
pinned_network = get_network_model(pinned_template)
exceptions = POM._collect_reduction_exceptions(
get_system(pinned),
pinned_network,
get_branch_models(pinned_template),
get_service_models(pinned_template),
)
@test Set(exceptions) == Set([1, 8])
@test 8 in keys(PNM.get_bus_reduction_map(get_network_reduction(pinned_network)))

# The retained branch is what the interface reads: its flow and the interface flow agree.
@test solve!(pinned) == IOM.RunStatus.SUCCESSFULLY_FINALIZED
container = IOM.get_optimization_container(pinned)
line_flow = IOM.get_expression(container, POM.BThetaBranchFlow, Line)
interface_flow =
IOM.get_expression(container, POM.InterfaceTotalFlow, TransmissionInterface)
@test "1-8-i_1" in axes(line_flow)[1]
for t in axes(interface_flow)[2]
@test isapprox(
JuMP.value(interface_flow["leaf_interface", t]),
JuMP.value(line_flow["1-8-i_1", t]);
atol = POM.ABSOLUTE_TOLERANCE,
)
end
end

@testset "Caller-supplied reduction_exceptions survive the build's reduction" begin
# Bus 8 hangs off a radial branch, so RadialReduction absorbs it into bus 1 unless
# something pins it. Asserted through a real build so the wiring from the
Expand Down
Loading
Loading