Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 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 @@ -27,7 +27,7 @@ PowerFlows = "94fada2c-fd9a-4e89-8d82-81405f5cb4f6"
[sources]
InfrastructureSystems = {rev = "IS4", url = "https://github.com/Sienna-Platform/InfrastructureSystems.jl"}
PowerSystems = {rev = "psy6", url = "https://github.com/Sienna-Platform/PowerSystems.jl"}
InfrastructureOptimizationModels = {rev = "main", url = "https://github.com/Sienna-Platform/InfrastructureOptimizationModels.jl"}
InfrastructureOptimizationModels = {rev = "ac/g1-port", url = "https://github.com/Sienna-Platform/InfrastructureOptimizationModels.jl"}
PowerNetworkMatrices = {rev = "psy6", url = "https://github.com/Sienna-Platform/PowerNetworkMatrices.jl"}

[extensions]
Expand Down
15 changes: 15 additions & 0 deletions src/PowerOperationsModels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ include("network_models/network_constructor.jl")
# Services Models
include("services_models/service_slacks.jl")
include("services_models/reserves.jl")
include("services_models/static_injection_security_constrained_models.jl")
include("services_models/reserve_group.jl")
# include("services_models/agc.jl") # TODO: needs _get_ace_error
include("services_models/transmission_interface.jl")
Expand Down Expand Up @@ -536,6 +537,8 @@ export FlowActivePowerSlackUpperBound
export FlowActivePowerSlackLowerBound
export PostContingencyFlowActivePowerSlackUpperBound
export PostContingencyFlowActivePowerSlackLowerBound
export PostContingencyActivePowerChangeVariable
export PostContingencyActivePowerReserveDeploymentVariable
export FlowActivePowerFromToVariable
export FlowActivePowerToFromVariable
export FlowReactivePowerFromToVariable
Expand Down Expand Up @@ -724,6 +727,11 @@ export FlowRateConstraint
export FlowRateConstraintFromTo
export FlowRateConstraintToFrom
export PostContingencyFlowRateConstraint
export PostContingencyGenerationBalanceConstraint
export PostContingencyActivePowerGenerationLimitsConstraint
export PostContingencyCopperPlateBalanceConstraint
export PostContingencyActivePowerVariableLimitsConstraint
export PostContingencyActivePowerReserveDeploymentVariableLimitsConstraint
export FlowLimitConstraint
export FlowLimitFromToConstraint
export FlowLimitToFromConstraint
Expand Down Expand Up @@ -777,6 +785,11 @@ export FuelConsumptionExpression
export ActivePowerRangeExpressionLB
export ActivePowerRangeExpressionUB
export PostContingencyBranchFlow
export PostContingencyAreaInterchangeFlow
export PostContingencyActivePowerGeneration
export PostContingencyActivePowerBalance
export PostContingencyNodalActivePowerDeployment
export PostContingencyAreaActivePowerDeployment
export NetActivePower
export DCCurrentBalance
export ComponentReserveUpBalanceExpression
Expand Down Expand Up @@ -874,6 +887,8 @@ export RangeReserve
export StepwiseCostReserve
export RampReserve
export NonSpinningReserve
export SecurityConstrainedContingencyReserve
export SecurityConstrainedRampReserve
export ConstantMaxInterfaceFlow
export VariableMaxInterfaceFlow

Expand Down
98 changes: 52 additions & 46 deletions src/ac_transmission_models/security_constrained_branch.jl
Original file line number Diff line number Diff line change
Expand Up @@ -267,10 +267,12 @@ function _has_other_v_container(
end

"""
Pre-allocate a `SparseAxisArray` keyed by
`(outage_id::String, monitored_name::String, t::Int)` holding `JuMP.AffExpr`
zeros for every entry produced by `_resolve_monitored_arcs`. The pre-fill is
required so the parallel PTDF expression build below cannot race on Dict resize.
Register a `SparseAxisArray` keyed by
`(outage_id::String, monitored_name::String, t::Int)`, prefilling one fresh
`zero(AffExpr)` per entry produced by `_resolve_monitored_arcs`. Prefilling the
resolved keys (like a dense container's slots) lets the build loop assign or
accumulate into existing entries; storage stays sparse — only resolved keys
exist, never the full outage×component×time product.
"""
function _add_post_contingency_sparse_expression!(
Comment thread
acostarelli marked this conversation as resolved.
Outdated
container::OptimizationContainer,
Expand All @@ -281,33 +283,30 @@ function _add_post_contingency_sparse_expression!(
},
time_steps::UnitRange{Int},
) where {T <: PostContingencyExpressions, V <: PSY.ACTransmission}
contents = Dict{Tuple{String, String, Int}, JuMP.AffExpr}()
for (uuid, entries) in resolved
outage_id = string(uuid)
for (_, name, _, _) in entries, t in time_steps
contents[(outage_id, name, t)] = zero(JuMP.AffExpr)
end
end
expr_container = SparseAxisArray(contents)
IOM._assign_container!(container.expressions, ExpressionKey(T, V), expr_container)
return expr_container
index_keys = [
(string(uuid), name, t)
for (uuid, entries) in resolved for (_, name, _, _) in entries for
t in time_steps
]
return IOM.add_expression_container!(container, T, V; sparse_keys = index_keys)
end

"""
Register an empty `SparseAxisArray` keyed by
Register a `SparseAxisArray` keyed by
`(outage_id::String, monitored_name::String, t::Int)` for the given constraint
type and meta tag.
type and meta tag, prefilling `index_keys` with `nothing` placeholders for the
build loop to assign into. An empty `index_keys` yields an empty container.
"""
function _add_post_contingency_sparse_constraints!(
Comment thread
acostarelli marked this conversation as resolved.
Outdated
container::OptimizationContainer,
::Type{T},
::Type{V};
::Type{V},
index_keys::AbstractVector{<:Tuple};
meta::String,
) where {T <: ConstraintType, V <: PSY.ACTransmission}
cons_container =
SparseAxisArray(Dict{Tuple{String, String, Int}, JuMP.ConstraintRef}())
IOM._assign_container!(container.constraints, ConstraintKey(T, V, meta), cons_container)
return cons_container
return IOM.add_constraints_container!(
container, T, V; sparse_keys = index_keys, meta = meta,
)
end

"""
Expand Down Expand Up @@ -412,15 +411,38 @@ function add_constraints!(

resolved = _resolve_monitored_arcs(device_model, net_reduction_data)

con_lb = _add_post_contingency_sparse_constraints!(container, T, V; meta = "lb")
con_ub = _add_post_contingency_sparse_constraints!(container, T, V; meta = "ub")
index_keys = [
(string(uuid), name, t)
for (uuid, entries) in resolved for (_, name, _, _) in entries for
t in time_steps
]
con_lb =
_add_post_contingency_sparse_constraints!(container, T, V, index_keys; meta = "lb")
con_ub =
_add_post_contingency_sparse_constraints!(container, T, V, index_keys; meta = "ub")

use_slacks = get_use_slacks(device_model)
# Local relaxation-slack containers keyed by `(outage_id, name, t)`. Built
# here (not via `add_variables!`) because the post-contingency axes are only
# known after `_resolve_monitored_arcs`; registered after the loop iff used.
slack_ub = SparseAxisArray(Dict{Tuple{String, String, Int}, JuMP.VariableRef}())
slack_lb = SparseAxisArray(Dict{Tuple{String, String, Int}, JuMP.VariableRef}())
# Relaxation-slack containers keyed by `(outage_id, name, t)`, registered only
# when slacks are enabled. Built here (not via `add_variables!`) because the
# post-contingency keys are only known after `_resolve_monitored_arcs`.
slack_ub =
Comment thread
acostarelli marked this conversation as resolved.
Outdated
if use_slacks
IOM.add_variable_container!(
container, PostContingencyFlowActivePowerSlackUpperBound, V;
sparse_keys = index_keys,
)
else
nothing
end
slack_lb =
if use_slacks
IOM.add_variable_container!(
container, PostContingencyFlowActivePowerSlackLowerBound, V;
sparse_keys = index_keys,
)
else
nothing
end

expressions = get_expression(container, PostContingencyBranchFlow, V)
jump_model = get_jump_model(container)
Expand Down Expand Up @@ -542,20 +564,6 @@ function add_constraints!(
end
end

if !isempty(slack_ub.data)
IOM._assign_container!(
container.variables,
VariableKey(PostContingencyFlowActivePowerSlackUpperBound, V),
slack_ub,
)
end
if !isempty(slack_lb.data)
IOM._assign_container!(
container.variables,
VariableKey(PostContingencyFlowActivePowerSlackLowerBound, V),
slack_lb,
)
end
return
end

Expand Down Expand Up @@ -713,10 +721,8 @@ function add_post_contingency_flow_expressions!(
time_steps = get_time_steps(container)
resolved = _resolve_monitored_arcs(model, network_model.network_reduction)

expression_container =
SparseAxisArray(Dict{Tuple{String, String, Int}, JuMP.AffExpr}())
IOM._assign_container!(
container.expressions, ExpressionKey(T, V), expression_container,
expression_container = _add_post_contingency_sparse_expression!(
container, T, V, resolved, time_steps,
)

has_other_v = _has_other_v_container(IOM.get_expressions(container), T, V)
Expand Down
66 changes: 66 additions & 0 deletions src/core/constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,46 @@ is the branch emergency rating (system base / per-unit).
"""
struct PostContingencyFlowRateConstraint <: PostContingencyConstraintType end

"""
Constraint that closes the per-contingency post-contingency generation balance:
the total active power shed by the outaged generators equals the total reserve
deployment across all contributing generators.

```math
\\sum_{g \\in \\mathcal{G}_c} p_{g,t} = \\sum_{g \\in \\mathcal{G}} \\Delta rsv_{c,g,t},
\\quad \\forall c \\in \\mathcal{C},\\ \\forall t
```
"""
struct PostContingencyGenerationBalanceConstraint <: PostContingencyConstraintType end

"""
Constraint on the post-contingency active power generation expression
(`PostContingencyActivePowerGeneration`) of each contributing generator
under each outage. Outaged generators are pinned to zero; all other
generators are bounded by their `active_power_limits`.

```math
P^{\\text{min}}_g \\le p_{g,t} + \\Delta rsv_{c,g,t} \\le P^{\\text{max}}_g,
\\quad \\forall c \\in \\mathcal{C},\\ \\forall g \\notin \\mathcal{G}_c,\\ \\forall t
```
"""
struct PostContingencyActivePowerGenerationLimitsConstraint <:
PostContingencyConstraintType end

"""
Constraint that closes the per-area post-contingency power balance for the
`AreaBalancePowerModel` network representation, summing the
`PostContingencyAreaActivePowerDeployment` with the pre-contingency
`ActivePowerBalance` expression for each area.

```math
\\sum_{g \\in \\mathcal{A}}(\\Delta rsv_{c,g,t} + p_{g,t}\\mathbb{1}_{g \\in \\mathcal{G}_c}) +
\\text{Bal}^{\\text{pre}}_{a,t} = 0,\\quad \\forall a \\in \\mathcal{A},\\ \\forall c,\\ \\forall t
```
"""
struct PostContingencyCopperPlateBalanceConstraint <:
PostContingencyConstraintType end

"""
Struct to create the constraint for branch flow rate limits from the 'from' bus to the 'to' bus.
For more information check [Branch Formulations](@ref PowerSystems.Branch-Formulations).
Expand Down Expand Up @@ -454,6 +494,32 @@ S_k^{\\max}`` for ``k \\in \\{f, t\\}`` via one of two shapes, selected by the
struct HVDCVSCApparentPowerLimitConstraint <: ConstraintType end

abstract type PowerVariableLimitsConstraint <: ConstraintType end

abstract type PostContingencyVariableLimitsConstraint <: PowerVariableLimitsConstraint end

"""
Struct to create the constraint to limit post-contingency active power expressions.
Comment thread
acostarelli marked this conversation as resolved.
Outdated

```math
P^\\text{min} \\le p_t + \\Delta p_{c, t} \\le P^\\text{max},
\\quad \\forall c \\in \\mathcal{C},\\ \\forall t
```
"""
struct PostContingencyActivePowerVariableLimitsConstraint <:
PostContingencyVariableLimitsConstraint end

"""
Struct to create the constraint to limit post-contingency active power reserve
deployment expressions.

```math
\\Delta rsv_{r, c, t} \\le rsv_{r, c, t},
\\quad \\forall r \\in \\mathcal{R},\\ \\forall c \\in \\mathcal{C},\\ \\forall t
```
"""
struct PostContingencyActivePowerReserveDeploymentVariableLimitsConstraint <:
PostContingencyVariableLimitsConstraint end

"""
Struct to create the constraint to limit active power input expressions.
For more information check [Device Formulations](@ref formulation_intro).
Expand Down
1 change: 1 addition & 0 deletions src/core/definitions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const UNSET_INI_TIME = Dates.DateTime(0)
const ABSOLUTE_TOLERANCE = 1.0e-3
const BALANCE_SLACK_COST = 1e6
const CONSTRAINT_VIOLATION_SLACK_COST = 2e5
const POST_CONTINGENCY_CONSTRAINT_VIOLATION_SLACK_COST = 1e5
const SERVICES_SLACK_COST = 1e5
const COST_EPSILON = 1e-3
const PTDF_ZERO_TOL = 1e-9
Expand Down
7 changes: 7 additions & 0 deletions src/core/expressions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ struct ComponentReserveUpBalanceExpression <: ExpressionType end
struct ComponentReserveDownBalanceExpression <: ExpressionType end
struct InterfaceTotalFlow <: ExpressionType end
struct PTDFBranchFlow <: ExpressionType end
abstract type PostContingencySystemBalanceExpressions <: SystemBalanceExpressions end
struct PostContingencyActivePowerBalance <: PostContingencySystemBalanceExpressions end
struct PostContingencyAreaInterchangeFlow <: PostContingencyExpressions end
Comment thread
acostarelli marked this conversation as resolved.
struct PostContingencyNodalActivePowerDeployment <: PostContingencyExpressions end
struct PostContingencyAreaActivePowerDeployment <: PostContingencyExpressions end
Comment thread
acostarelli marked this conversation as resolved.
struct RealizedShiftedLoad <: ExpressionType end

#################################################################################
Expand Down Expand Up @@ -118,6 +122,7 @@ struct StorageReserveBalanceExpression{D, S, Sd} <:
# Method extensions for output writing
should_write_resulting_value(::Type{InterfaceTotalFlow}) = true
should_write_resulting_value(::Type{PTDFBranchFlow}) = true
should_write_resulting_value(::Type{PostContingencyAreaInterchangeFlow}) = true
should_write_resulting_value(::Type{RealizedShiftedLoad}) = true

should_write_resulting_value(::Type{HydroServedReserveUpExpression}) = true
Expand All @@ -133,5 +138,7 @@ should_write_resulting_value(
# Method extensions for unit conversion
convert_output_to_natural_units(::Type{InterfaceTotalFlow}) = true
convert_output_to_natural_units(::Type{PostContingencyBranchFlow}) = true
convert_output_to_natural_units(::Type{PostContingencyAreaInterchangeFlow}) = true
convert_output_to_natural_units(::Type{PostContingencyActivePowerGeneration}) = true
convert_output_to_natural_units(::Type{PTDFBranchFlow}) = true
convert_output_to_natural_units(::Type{RealizedShiftedLoad}) = true
30 changes: 30 additions & 0 deletions src/core/formulations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,36 @@ struct RampReserve <: AbstractReservesFormulation end
Struct to add non spinning reserve requirements larger than specified requirement
"""
struct NonSpinningReserve <: AbstractReservesFormulation end

abstract type AbstractSecurityConstrainedReservesFormulation <: AbstractReservesFormulation end

"""
Security-constrained contingency reserve formulation: deploys reserves under
each G-1 outage scoped to the reserve `PSY.Service`. The set of contingencies a
service responds to is the `PSY.Outage` supplemental attributes attached to that
service via `add_supplemental_attribute!(sys, service, outage)`; template
validation mirrors those attachments into `service_model.outages`.
A `RequirementTimeSeriesParameter` is optional: when present the requirement /
ramp / participation stack is built; when absent, per-generator
`PostContingencyActivePowerGeneration` limits are applied instead.
Post-contingency branch-flow constraints are added only for the monitored
components listed on each outage's `monitored_components`.

See also `SecurityConstrainedRampReserve`.
"""
struct SecurityConstrainedContingencyReserve <:
AbstractSecurityConstrainedReservesFormulation end

"""
Security-constrained ramp reserve formulation: like `RampReserve` for the
pre-contingency requirement/ramp/participation constraints, plus the same
G-1 post-contingency deployment + monitored-branch flow constraints as
`SecurityConstrainedContingencyReserve`.

See also `SecurityConstrainedContingencyReserve`.
"""
struct SecurityConstrainedRampReserve <:
AbstractSecurityConstrainedReservesFormulation end
"""
Struct to add a constant maximum transmission flow for specified interface
"""
Expand Down
Loading
Loading