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
22 changes: 10 additions & 12 deletions src/common_models/add_expressions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -218,15 +218,10 @@ function add_expressions!(
end

"""
Per-service cost expression container (e.g. `ProductionCostExpression` for ORDC reserves).
One container per service, `meta`-keyed by the service name, matching the reads in
Merged cost-expression container for reserve services (e.g. `ProductionCostExpression` for ORDC):
one dense `(service_name, time)` container per service type, empty meta - the same shape as the
device `ProductionCostExpression`. Read/written by
`add_to_expression!(container, ::CostExpressions, cost, ::ReserveDemand*, t)`.

TODO(services PWL cost): this per-service `meta` keying is temporary. It will be
removed/folded to a per-type dense `(service, time)` container (like the device
`ProductionCostExpression`) once the 3D/4D PWL cost containers for services land. Kept on
`meta` with the rest of the ORDC cost path (slope/breakpoint params, delta-PWL machinery)
until that migration.
"""
function add_expressions!(
container::OptimizationContainer,
Expand All @@ -240,9 +235,12 @@ function add_expressions!(
W <: AbstractReservesFormulation,
} where {D <: PSY.Component}
time_steps = get_time_steps(container)
for service in services
name = PSY.get_name(service)
add_expression_container!(container, T, D, [name], time_steps; meta = name)
end
add_expression_container!(
container,
T,
D,
[PSY.get_name(s) for s in services],
time_steps,
)
return
end
16 changes: 8 additions & 8 deletions src/common_models/add_to_expression.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2021,7 +2021,10 @@ function add_to_expression!(
}
expression = get_expression(container, InterfaceTotalFlow, PSY.TransmissionInterface)
service_name = PSY.get_name(service)
variable = get_variable(container, T, PSY.TransmissionInterface, service_name)
# DELETE-AFTER-REVIEW: reviewer context on the container change; remove once the PR is approved.
# Merged slack container keyed `(interface_name, time)`; read the whole container and index
# this interface's slice (was a per-service `meta = service_name` container).
variable = get_variable(container, T, PSY.TransmissionInterface)
time_steps = get_time_steps(container)
for t in time_steps
add_proportional_to_jump_expression!(
Expand Down Expand Up @@ -2974,10 +2977,8 @@ function add_to_expression!(
return
end

# TODO(services PWL cost): reads the per-service `meta = service_name` ORDC cost-expression
# container. Update to the per-type folded container when the 3D/4D PWL cost containers for
# services land (see the create site in add_expressions.jl and the ORDC slope/breakpoint
# params in add_parameters.jl -- all kept on `meta` together until that migration).
# Merged dense `(service_name, time)` ORDC cost-expression container (empty meta), read/written
# by service name - same shape as the device `ProductionCostExpression` above.
function add_to_expression!(
container::OptimizationContainer,
::Type{S},
Expand All @@ -2988,9 +2989,8 @@ function add_to_expression!(
S <: CostExpressions,
T <: Union{PSY.ReserveDemandCurve, PSY.ReserveDemandTimeSeriesCurve},
}
if has_container_key(container, S, T, PSY.get_name(component))
device_cost_expression =
get_expression(container, S, T, PSY.get_name(component))
if has_container_key(container, S, T)
device_cost_expression = get_expression(container, S, T)
component_name = PSY.get_name(component)
JuMP.add_to_expression!(
device_cost_expression[component_name, time_period],
Expand Down
17 changes: 9 additions & 8 deletions src/core/problem_template.jl
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,8 @@ function _populate_contributing_devices!(
# populating each reserve we require at least one such device: a modeled reserve with no
# available provider can never meet its requirement - it would silently force slacks or
# make the model infeasible - so error loudly and name it rather than dropping it.
# Non-reserve services (ConstantReserveGroup, TransmissionInterface, AGC) draw on other
# services or branches, not provider devices, so they are exempt from the check.
# A modeled TransmissionInterface likewise needs at least one available contributing branch,
# or its flow limit is meaningless. ConstantReserveGroup is exempt (see the check below).
for (service_key, service_model) in service_models
@debug "Populating service model $(service_key)"
empty!(get_contributing_devices_map(service_model))
Expand All @@ -265,14 +265,15 @@ function _populate_contributing_devices!(
)
end
end
# TODO(transmission interface, Q5): the check is reserve-scoped, so a
# TransmissionInterface with no contributing branches (or all-unavailable
# branches) still populates silently. Extend an equivalent loud error to the
# interface path when the interface migration lands.
if service_type <: PSY.Reserve &&
# Exempt ConstantReserveGroup: a GroupReserve aggregates other services, so its
# contributing-device map is empty by design; without this the error would fire on
# every group reserve (the bug that previously made ConstantReserveGroup unbuildable).
# Reserves and transmission interfaces DO draw on devices/branches, so an empty map
# there is a real misconfiguration. AGC is not modeled in POM, so it never reaches here.
if !(service_type <: PSY.ConstantReserveGroup) &&
isempty(get_contributing_devices_map(service_model, service_name))
error(
"Reserve service \"$(service_name)\" of type $(typeof(service)) has no available contributing devices. Assign available contributing devices to it in the system data, or remove its service model from the template.",
"Service \"$(service_name)\" of type $(typeof(service)) has no available contributing devices/branches. Assign available contributing devices/branches to it in the system data, or remove its service model from the template.",
)
end
end
Expand Down
47 changes: 27 additions & 20 deletions src/services_models/service_slacks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,32 +26,39 @@ end

function transmission_interface_slacks!(
container::OptimizationContainer,
service::T,
services::Vector{T},
) where {T <: PSY.TransmissionInterface}
time_steps = get_time_steps(container)
name = PSY.get_name(service)
interface_names = [PSY.get_name(s) for s in services]
jump_model = get_jump_model(container)

# DELETE-AFTER-REVIEW: reviewer context on the container change; remove once the PR is approved.
# One dense 2D container per (slack variable type, TransmissionInterface) keyed
# `[interface_name, time]`, built once over all interfaces (`use_slacks` is per type),
# empty meta. Each interface's slacks carry its own violation penalty.
for variable_type in [InterfaceFlowSlackUp, InterfaceFlowSlackDown]
variable = add_variable_container!(
container,
variable_type,
T,
[name],
time_steps;
meta = name,
)
penalty = PSY.get_violation_penalty(service)
for t in time_steps
variable[name, t] = JuMP.@variable(
get_jump_model(container),
base_name = "$(T)_$(variable_type)_{$(name), $(t)}",
)
JuMP.set_lower_bound(variable[name, t], 0.0)

add_to_objective_invariant_expression!(
variable =
add_variable_container!(
container,
variable[name, t] * penalty,
variable_type,
T,
interface_names,
time_steps,
)
for service in services
name = PSY.get_name(service)
penalty = PSY.get_violation_penalty(service)
for t in time_steps
variable[name, t] = JuMP.@variable(
jump_model,
base_name = "$(T)_$(variable_type)_{$(name), $(t)}",
lower_bound = 0.0,
)
add_to_objective_invariant_expression!(
container,
variable[name, t] * penalty,
)
end
end
end

Expand Down
42 changes: 22 additions & 20 deletions src/services_models/services_constructor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,8 @@ function construct_service!(
services,
StepwiseCostReserve(),
)
# Merged dense `(service, time)` cost-expression container, built once over all services.
add_expressions!(container, ProductionCostExpression, services, model)
for service in services
contributing_devices = get_contributing_devices(model, PSY.get_name(service))
add_service_variables!(
Expand All @@ -256,7 +258,6 @@ function construct_service!(
model,
devices_template,
)
add_expressions!(container, ProductionCostExpression, [service], model)
end
return
end
Expand Down Expand Up @@ -607,18 +608,17 @@ function construct_service!(
incompatible_device_types::Set{<:DataType},
network_model::NetworkModel{<:AbstractNetworkModel},
) where {T <: PSY.TransmissionInterface}
interfaces = get_available_components(model, sys)
interfaces = collect(get_available_components(model, sys))
# Lazy container addition for the expressions.
lazy_container_addition!(container, InterfaceTotalFlow,
T,
PSY.get_name.(interfaces),
get_time_steps(container),
)
if get_use_slacks(model)
transmission_interface_slacks!(container, interfaces)
end
for interface in interfaces
if get_use_slacks(model)
# Adding the slacks can be done in a cleaner fashion
transmission_interface_slacks!(container, interface)
end
add_feedforward_arguments!(container, model, interface)
end
return
Expand All @@ -633,19 +633,18 @@ function construct_service!(
incompatible_device_types::Set{<:DataType},
network_model::NetworkModel{AreaBalanceNetworkModel},
)
interfaces = get_available_components(model, sys)
interfaces = collect(get_available_components(model, sys))
# Lazy container addition for the expressions.
lazy_container_addition!(container, InterfaceTotalFlow,
PSY.TransmissionInterface,
PSY.get_name.(interfaces),
get_time_steps(container),
)
@warn "AreaBalanceNetworkModel doesn't model individual line flows and it ignores the flows on AC Transmission Devices"
if get_use_slacks(model)
transmission_interface_slacks!(container, interfaces)
end
for interface in interfaces
if get_use_slacks(model)
# Adding the slacks can be done in a cleaner fashion
transmission_interface_slacks!(container, interface)
end
add_feedforward_arguments!(container, model, interface)
end
return
Expand Down Expand Up @@ -864,7 +863,7 @@ function construct_service!(
incompatible_device_types::Set{<:DataType},
network_model::NetworkModel{<:AbstractNetworkModel},
)
interfaces = get_available_components(model, sys)
interfaces = collect(get_available_components(model, sys))
# Lazy container addition for the expressions.
lazy_container_addition!(container, InterfaceTotalFlow,
PSY.TransmissionInterface,
Expand All @@ -877,22 +876,25 @@ function construct_service!(
"Not all TransmissionInterfaces devices have time series. Check data to complete (or remove) time series.",
)
end
for interface in interfaces
if get_use_slacks(model)
# Adding the slacks can be done in a cleaner fashion
transmission_interface_slacks!(container, interface)
end
if all(has_ts)
if get_use_slacks(model)
transmission_interface_slacks!(container, interfaces)
end
if !isempty(interfaces) && all(has_ts)
for interface in interfaces
name = PSY.get_name(interface)
num_ts = length(unique(PSY.get_name.(PSY.get_time_series_keys(interface))))
if num_ts < 2
error(
"TransmissionInterface $name has less than two time series. It is required to add both min_flow and max_flow time series.",
)
end
add_parameters!(container, MinInterfaceFlowLimitParameter, interface, model)
add_parameters!(container, MaxInterfaceFlowLimitParameter, interface, model)
end
# Merged per-type parameter containers over all interfaces (empty meta), filled per
# interface by the vector `_add_parameters!` path.
add_parameters!(container, MinInterfaceFlowLimitParameter, interfaces, model)
add_parameters!(container, MaxInterfaceFlowLimitParameter, interfaces, model)
end
for interface in interfaces
add_feedforward_arguments!(container, model, interface)
end
return
Expand Down
18 changes: 12 additions & 6 deletions src/services_models/transmission_interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -101,17 +101,23 @@ function add_constraints!(
meta = "lb",
)
int_name = PSY.get_name(interface)
# DELETE-AFTER-REVIEW: reviewer context on the container change; remove once the PR is approved.
# Merged per-type parameter containers keyed by interface name (empty meta); fetch the whole
# container/multiplier array and index this interface's column/row (was a per-service
# `meta = int_name` container).
param_container_min =
get_parameter(container, MinInterfaceFlowLimitParameter, PSY.TransmissionInterface, int_name)
param_multiplier_min = get_parameter_multiplier_array(container, MinInterfaceFlowLimitParameter,
get_parameter(container, MinInterfaceFlowLimitParameter, PSY.TransmissionInterface)
param_multiplier_min = get_parameter_multiplier_array(
container,
MinInterfaceFlowLimitParameter,
PSY.TransmissionInterface,
int_name,
)
param_container_max =
get_parameter(container, MaxInterfaceFlowLimitParameter, PSY.TransmissionInterface, int_name)
param_multiplier_max = get_parameter_multiplier_array(container, MaxInterfaceFlowLimitParameter,
get_parameter(container, MaxInterfaceFlowLimitParameter, PSY.TransmissionInterface)
param_multiplier_max = get_parameter_multiplier_array(
container,
MaxInterfaceFlowLimitParameter,
PSY.TransmissionInterface,
int_name,
)
param_min = get_parameter_column_refs(param_container_min, int_name)
param_max = get_parameter_column_refs(param_container_max, int_name)
Expand Down
Loading
Loading