add flexiramp

pull/21/head
oyurdakul 4 years ago
parent 5e2cdb9e0c
commit a3a71ff5a9

BIN
.DS_Store vendored

Binary file not shown.

@ -36,6 +36,7 @@ This section describes system-wide parameters, such as power balance and reserve
| `Time step (min)` | Length of each time step (in minutes). Must be a divisor of 60 (e.g. 60, 30, 20, 15, etc). | `60` | N | `Time step (min)` | Length of each time step (in minutes). Must be a divisor of 60 (e.g. 60, 30, 20, 15, etc). | `60` | N
| `Power balance penalty ($/MW)` | Penalty for system-wide shortage or surplus in production (in $/MW). This is charged per time step. For example, if there is a shortage of 1 MW for three time steps, three times this amount will be charged. | `1000.0` | Y | `Power balance penalty ($/MW)` | Penalty for system-wide shortage or surplus in production (in $/MW). This is charged per time step. For example, if there is a shortage of 1 MW for three time steps, three times this amount will be charged. | `1000.0` | Y
| `Reserve shortfall penalty ($/MW)` | Penalty for system-wide shortage in meeting reserve requirements (in $/MW). This is charged per time step. Negative value implies reserve constraints must always be satisfied. | `-1` | Y | `Reserve shortfall penalty ($/MW)` | Penalty for system-wide shortage in meeting reserve requirements (in $/MW). This is charged per time step. Negative value implies reserve constraints must always be satisfied. | `-1` | Y
| `Flexiramp penalty ($/MW)` | Penalty for system-wide shortage in meeting flexible ramping product requirements (in $/MW). This is charged per time step. | `500` | Y
#### Example #### Example
@ -44,7 +45,8 @@ This section describes system-wide parameters, such as power balance and reserve
"Parameters": { "Parameters": {
"Time horizon (h)": 4, "Time horizon (h)": 4,
"Power balance penalty ($/MW)": 1000.0, "Power balance penalty ($/MW)": 1000.0,
"Reserve shortfall penalty ($/MW)": -1.0 "Reserve shortfall penalty ($/MW)": -1.0,
"Flexiramp penalty ($/MW)": 100.0
} }
} }
``` ```
@ -97,6 +99,8 @@ This section describes all generators in the system, including thermal units, re
| `Initial power (MW)` | Amount of power the generator at time step `-1`, immediately before the planning horizon starts. | Required | N | `Initial power (MW)` | Amount of power the generator at time step `-1`, immediately before the planning horizon starts. | Required | N
| `Must run?` | If `true`, the generator should be committed, even if that is not economical (Boolean). | `false` | Y | `Must run?` | If `true`, the generator should be committed, even if that is not economical (Boolean). | `false` | Y
| `Provides spinning reserves?` | If `true`, this generator may provide spinning reserves (Boolean). | `true` | Y | `Provides spinning reserves?` | If `true`, this generator may provide spinning reserves (Boolean). | `true` | Y
| `Provides flexible capacity?` | If `true`, this generator may provide flexible ramping product (Boolean). | `true` | Y
#### Production costs and limits #### Production costs and limits
@ -136,6 +140,7 @@ Note that this curve also specifies the production limits. Specifically, the fir
"Initial status (h)": 12, "Initial status (h)": 12,
"Must run?": false, "Must run?": false,
"Provides spinning reserves?": true, "Provides spinning reserves?": true,
"Provides flexible capacity?": false,
}, },
"gen2": { "gen2": {
"Bus": "b5", "Bus": "b5",
@ -206,14 +211,16 @@ This section describes the characteristics of transmission system, such as its t
### Reserves ### Reserves
This section describes the hourly amount of operating reserves required. This section describes the hourly amount of reserves required.
| Key | Description | Default | Time series? | Key | Description | Default | Time series?
| :-------------------- | :------------------------------------------------- | --------- | :----: | :-------------------- | :------------------------------------------------- | --------- | :----:
| `Spinning (MW)` | Minimum amount of system-wide spinning reserves (in MW). Only generators which are online may provide this reserve. | `0.0` | Y | `Spinning (MW)` | Minimum amount of system-wide spinning reserves (in MW). Only generators which are online may provide this reserve. | `0.0` | Y
| `Up-flexiramp (MW)` | Minimum amount of system-wide upward flexible ramping product (in MW). Only generators which are online may provide this reserve. | `0.0` | Y
| `Down-flexiramp (MW)` | Minimum amount of system-wide downward flexible ramping product (in MW). Only generators which are online may provide this reserve. | `0.0` | Y
#### Example #### Example 1
```json ```json
{ {
@ -228,6 +235,27 @@ This section describes the hourly amount of operating reserves required.
} }
``` ```
#### Example 2
```json
{
"Reserves": {
"up-flexiramp (MW)": [
20.31042,
23.65273,
27.41784,
25.34057
],
"down-flexiramp (MW)": [
19.41546,
21.45377,
23.53402,
24.80973
]
}
}
```
### Contingencies ### Contingencies
This section describes credible contingency scenarios in the optimization, such as the loss of a transmission line or generator. This section describes credible contingency scenarios in the optimization, such as the loss of a transmission line or generator.
@ -287,6 +315,7 @@ Current limitations
------------------- -------------------
* All reserves are system-wide. Zonal reserves are not currently supported. * All reserves are system-wide. Zonal reserves are not currently supported.
* Upward and downward flexible ramping products can only be acquired under the WanHob2016 formulation, which does not support spinning reserves.
* Network topology remains the same for all time periods * Network topology remains the same for all time periods
* Only N-1 transmission contingencies are supported. Generator contingencies are not currently supported. * Only N-1 transmission contingencies are supported. Generator contingencies are not currently supported.
* Time-varying minimum production amounts are not currently compatible with ramp/startup/shutdown limits. * Time-varying minimum production amounts are not currently compatible with ramp/startup/shutdown limits.

@ -16,6 +16,7 @@ include("model/formulations/KnuOstWat2018/structs.jl")
include("model/formulations/MorLatRam2013/structs.jl") include("model/formulations/MorLatRam2013/structs.jl")
include("model/formulations/PanGua2016/structs.jl") include("model/formulations/PanGua2016/structs.jl")
include("solution/methods/XavQiuWanThi2019/structs.jl") include("solution/methods/XavQiuWanThi2019/structs.jl")
include("model/formulations/WanHob2016/structs.jl")
include("import/egret.jl") include("import/egret.jl")
include("instance/read.jl") include("instance/read.jl")
@ -36,6 +37,7 @@ include("model/formulations/KnuOstWat2018/pwlcosts.jl")
include("model/formulations/MorLatRam2013/ramp.jl") include("model/formulations/MorLatRam2013/ramp.jl")
include("model/formulations/MorLatRam2013/scosts.jl") include("model/formulations/MorLatRam2013/scosts.jl")
include("model/formulations/PanGua2016/ramp.jl") include("model/formulations/PanGua2016/ramp.jl")
include("model/formulations/WanHob2016/ramp.jl")
include("model/jumpext.jl") include("model/jumpext.jl")
include("solution/fix.jl") include("solution/fix.jl")
include("solution/methods/XavQiuWanThi2019/enforce.jl") include("solution/methods/XavQiuWanThi2019/enforce.jl")

@ -108,6 +108,11 @@ function _from_json(json; repair = true)
json["Parameters"]["Power balance penalty (\$/MW)"], json["Parameters"]["Power balance penalty (\$/MW)"],
default = [1000.0 for t in 1:T], default = [1000.0 for t in 1:T],
) )
# Penalty price for shortage in meeting system-wide flexiramp requirements
flexiramp_shortfall_penalty = timeseries(
json["Parameters"]["Flexiramp penalty (\$/MW)"],
default = [500.0 for t in 1:T],
)
shortfall_penalty = timeseries( shortfall_penalty = timeseries(
json["Parameters"]["Reserve shortfall penalty (\$/MW)"], json["Parameters"]["Reserve shortfall penalty (\$/MW)"],
default = [-1.0 for t in 1:T], default = [-1.0 for t in 1:T],
@ -200,6 +205,10 @@ function _from_json(json; repair = true)
dict["Provides spinning reserves?"], dict["Provides spinning reserves?"],
default = [true for t in 1:T], default = [true for t in 1:T],
), ),
timeseries(
dict["Provides flexible capacity?"],
default = [true for t in 1:T],
),
startup_categories, startup_categories,
) )
push!(bus.units, unit) push!(bus.units, unit)
@ -207,12 +216,16 @@ function _from_json(json; repair = true)
push!(units, unit) push!(units, unit)
end end
# Read reserves # Read spinning, up-flexiramp, and down-flexiramp reserve requirements
reserves = Reserves(zeros(T)) reserves = Reserves(zeros(T), zeros(T), zeros(T))
if "Reserves" in keys(json) if "Reserves" in keys(json)
reserves.spinning = reserves.spinning =
timeseries(json["Reserves"]["Spinning (MW)"], default = zeros(T)) timeseries(json["Reserves"]["Spinning (MW)"], default = zeros(T))
end reserves.upflexiramp =
timeseries(json["Reserves"]["Up-flexiramp (MW)"], default = zeros(T))
reserves.dwflexiramp =
timeseries(json["Reserves"]["Down-flexiramp (MW)"], default = zeros(T))
end
# Read transmission lines # Read transmission lines
if "Transmission lines" in keys(json) if "Transmission lines" in keys(json)
@ -287,6 +300,7 @@ function _from_json(json; repair = true)
price_sensitive_loads = loads, price_sensitive_loads = loads,
reserves = reserves, reserves = reserves,
shortfall_penalty = shortfall_penalty, shortfall_penalty = shortfall_penalty,
flexiramp_shortfall_penalty = flexiramp_shortfall_penalty,
time = T, time = T,
units_by_name = Dict(g.name => g for g in units), units_by_name = Dict(g.name => g for g in units),
units = units, units = units,

@ -37,6 +37,7 @@ mutable struct Unit
initial_status::Union{Int,Nothing} initial_status::Union{Int,Nothing}
initial_power::Union{Float64,Nothing} initial_power::Union{Float64,Nothing}
provides_spinning_reserves::Vector{Bool} provides_spinning_reserves::Vector{Bool}
provides_flexiramp_reserves::Vector{Bool} # binary variable indicating whether the unit provides flexiramp
startup_categories::Vector{StartupCategory} startup_categories::Vector{StartupCategory}
end end
@ -54,6 +55,8 @@ end
mutable struct Reserves mutable struct Reserves
spinning::Vector{Float64} spinning::Vector{Float64}
upflexiramp::Vector{Float64} # up-flexiramp reserve requirements
dwflexiramp::Vector{Float64} # down-flexiramp reserve requirements
end end
mutable struct Contingency mutable struct Contingency
@ -81,6 +84,7 @@ Base.@kwdef mutable struct UnitCommitmentInstance
price_sensitive_loads::Vector{PriceSensitiveLoad} price_sensitive_loads::Vector{PriceSensitiveLoad}
reserves::Reserves reserves::Reserves
shortfall_penalty::Vector{Float64} shortfall_penalty::Vector{Float64}
flexiramp_shortfall_penalty::Vector{Float64} # penalty price for flexiramp shortfall
time::Int time::Int
units_by_name::Dict{AbstractString,Unit} units_by_name::Dict{AbstractString,Unit}
units::Vector{Unit} units::Vector{Unit}

BIN
src/model/.DS_Store vendored

Binary file not shown.

@ -32,6 +32,14 @@ function build_model(;
formulation = Formulation(), formulation = Formulation(),
variable_names::Bool = false, variable_names::Bool = false,
)::JuMP.Model )::JuMP.Model
if formulation.ramping ==WanHob2016.Ramping() && instance.reserves.spinning!=zeros(instance.time)
error("Spinning reserves are not supported by the WanHob2016 ramping formulation")
end
@show formulation.ramping
if formulation.ramping !== WanHob2016.Ramping() && (instance.reserves.upflexiramp!=zeros(instance.time) || instance.reserves.dwflexiramp!=zeros(instance.time))
error("Flexiramp is supported only by the WanHob2016 ramping formulation")
end
@info "Building model..." @info "Building model..."
time_model = @elapsed begin time_model = @elapsed begin
model = Model() model = Model()

@ -0,0 +1,152 @@
# UnitCommitmentFL.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
function _add_flexiramp_vars!(model::JuMP.Model, g::Unit)::Nothing
upflexiramp = _init(model, :upflexiramp)
upflexiramp_shortfall = _init(model, :upflexiramp_shortfall)
mfg=_init(model,:mfg)
dwflexiramp = _init(model, :dwflexiramp)
dwflexiramp_shortfall = _init(model, :dwflexiramp_shortfall)
for t in 1:model[:instance].time
# maximum feasible generation, \bar{g_{its}} in Wang & Hobbs (2016)
mfg[g.name,t]=@variable(model, lower_bound = 0)
if g.provides_flexiramp_reserves[t]
upflexiramp[g.name, t] = @variable(model) # up-flexiramp, ur_{it} in Wang & Hobbs (2016)
dwflexiramp[g.name, t] = @variable(model) # down-flexiramp, dr_{it} in Wang & Hobbs (2016)
else
upflexiramp[g.name, t] = 0.0
dwflexiramp[g.name, t] = 0.0
end
upflexiramp_shortfall[t] =
(model[:instance].flexiramp_shortfall_penalty[t] >= 0) ?
@variable(model, lower_bound = 0) : 0.0
dwflexiramp_shortfall[t] =
(model[:instance].flexiramp_shortfall_penalty[t] >= 0) ?
@variable(model, lower_bound = 0) : 0.0
end
return
end
function _add_ramp_eqs!(
model::JuMP.Model,
g::Unit,
formulation_prod_vars::Gar1962.ProdVars,
formulation_ramping::WanHob2016.Ramping,
formulation_status_vars::Gar1962.StatusVars,
)::Nothing
is_initially_on = (g.initial_status > 0)
SU = g.startup_limit
SD = g.shutdown_limit
RU = g.ramp_up_limit
RD = g.ramp_down_limit
gn = g.name
minp=g.min_power
maxp=g.max_power
initial_power=g.initial_power
is_on = model[:is_on]
prod_above = model[:prod_above]
upflexiramp=model[:upflexiramp]
dwflexiramp=model[:dwflexiramp]
mfg=model[:mfg]
for t in 1:model[:instance].time
@constraint(model, prod_above[gn, t] + (is_on[gn,t]*minp[t])
<=mfg[gn,t]) # Eq. (19) in Wang & Hobbs (2016)
@constraint(model, mfg[gn,t]<= is_on[gn,t]* maxp[t]) # Eq. (22) in Wang & Hobbs (2016)
if t!=model[:instance].time
@constraint(model, minp[t] * (is_on[gn,t+1]+is_on[gn,t]-1) <=
prod_above[gn, t] - dwflexiramp[gn,t] +(is_on[gn,t]*minp[t])
) # first inequality of Eq. (20) in Wang & Hobbs (2016)
@constraint(model, prod_above[gn, t] - dwflexiramp[gn,t] + (is_on[gn,t]*minp[t]) <=
mfg[gn,t+1]
+ (maxp[t] * (1-is_on[gn,t+1]))
) # second inequality of Eq. (20) in Wang & Hobbs (2016)
@constraint(model, minp[t] * (is_on[gn,t+1]+is_on[gn,t]-1) <=
prod_above[gn, t] + upflexiramp[gn,t] + (is_on[gn,t]*minp[t])
) # first inequality of Eq. (21) in Wang & Hobbs (2016)
@constraint(model, prod_above[gn, t] + upflexiramp[gn,t] +(is_on[gn,t]*minp[t]) <=
mfg[gn,t+1] + (maxp[t] * (1-is_on[gn,t+1]))
) # second inequality of Eq. (21) in Wang & Hobbs (2016)
if t!=1
@constraint(model, mfg[gn,t]<=prod_above[gn,t-1] + (is_on[gn,t-1]*minp[t])
+ (RU * is_on[gn,t-1])
+ (SU*(is_on[gn,t] - is_on[gn,t-1]))
+ maxp[t] * (1-is_on[gn,t])
) # Eq. (23) in Wang & Hobbs (2016)
@constraint(model, (prod_above[gn,t-1] + (is_on[gn,t-1]*minp[t]))
- (prod_above[gn,t] + (is_on[gn,t]*minp[t]))
<= RD * is_on[gn,t]
+ SD * (is_on[gn,t-1] - is_on[gn,t])
+ maxp[t] * (1-is_on[gn,t-1])
) # Eq. (25) in Wang & Hobbs (2016)
else
@constraint(model, mfg[gn,t]<=initial_power
+ (RU * is_initially_on)
+ (SU*(is_on[gn,t] - is_initially_on))
+ maxp[t] * (1-is_on[gn,t])
) # Eq. (23) in Wang & Hobbs (2016) for the first time period
@constraint(model, initial_power
- (prod_above[gn,t] + (is_on[gn,t]*minp[t]))
<= RD * is_on[gn,t]
+ SD * (is_initially_on - is_on[gn,t])
+ maxp[t] * (1-is_initially_on)
) # Eq. (25) in Wang & Hobbs (2016) for the first time period
end
@constraint(model, mfg[gn,t]<=
(SD*(is_on[gn,t] - is_on[gn,t+1]))
+ (maxp[t] * is_on[gn,t+1])
) # Eq. (24) in Wang & Hobbs (2016)
@constraint(model, -RD * is_on[gn,t+1]
-SD * (is_on[gn,t]-is_on[gn,t+1])
-maxp[t] * (1-is_on[gn,t])
<= upflexiramp[gn,t]
) # first inequality of Eq. (26) in Wang & Hobbs (2016)
@constraint(model, upflexiramp[gn,t] <=
RU * is_on[gn,t]
+ SU * (is_on[gn,t+1]-is_on[gn,t])
+ maxp[t] * (1-is_on[gn,t+1])
) # second inequality of Eq. (26) in Wang & Hobbs (2016)
@constraint(model, -RU * is_on[gn,t]
-SU * (is_on[gn,t+1]-is_on[gn,t])
-maxp[t] * (1-is_on[gn,t+1])
<= dwflexiramp[gn,t]
) # first inequality of Eq. (27) in Wang & Hobbs (2016)
@constraint(model, dwflexiramp[gn,t] <=
RD * is_on[gn,t+1]
+ SD * (is_on[gn,t]-is_on[gn,t+1])
+ maxp[t] * (1-is_on[gn,t])
) # second inequality of Eq. (27) in Wang & Hobbs (2016)
@constraint(model, -maxp[t] * is_on[gn,t]
+minp[t] * is_on[gn,t+1]
<= upflexiramp[gn,t]
) # first inequality of Eq. (28) in Wang & Hobbs (2016)
@constraint(model, upflexiramp[gn,t] <=
maxp[t] * is_on[gn,t+1]
) # second inequality of Eq. (28) in Wang & Hobbs (2016)
@constraint(model, -maxp[t] * is_on[gn,t+1]
<= dwflexiramp[gn,t]
) # first inequality of Eq. (29) in Wang & Hobbs (2016)
@constraint(model, dwflexiramp[gn,t] <=
(maxp[t] * is_on[gn,t])
-(minp[t] * is_on[gn,t+1])
) # second inequality of Eq. (29) in Wang & Hobbs (2016)
else
@constraint(model, mfg[gn,t]<=prod_above[gn,t-1] + (is_on[gn,t-1]*minp[t])
+ (RU * is_on[gn,t-1])
+ (SU*(is_on[gn,t] - is_on[gn,t-1]))
+ maxp[t] * (1-is_on[gn,t])
) # Eq. (23) in Wang & Hobbs (2016) for the last time period
@constraint(model, (prod_above[gn,t-1] + (is_on[gn,t-1]*minp[t]))
- (prod_above[gn,t] + (is_on[gn,t]*minp[t]))
<= RD * is_on[gn,t]
+ SD * (is_on[gn,t-1] - is_on[gn,t])
+ maxp[t] * (1-is_on[gn,t-1])
) # Eq. (25) in Wang & Hobbs (2016) for the last time period
end
end
end

@ -0,0 +1,18 @@
# UnitCommitmentFL.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
"""
Formulation described in:
B. Wang and B. F. Hobbs, "Real-Time Markets for Flexiramp: A Stochastic
Unit Commitment-Based Analysis," in IEEE Transactions on Power Systems,
vol. 31, no. 2, pp. 846-860, March 2016, doi: 10.1109/TPWRS.2015.2411268.
"""
module WanHob2016
import ..RampingFormulation
struct Ramping <: RampingFormulation end
end

@ -5,6 +5,7 @@
function _add_system_wide_eqs!(model::JuMP.Model)::Nothing function _add_system_wide_eqs!(model::JuMP.Model)::Nothing
_add_net_injection_eqs!(model) _add_net_injection_eqs!(model)
_add_reserve_eqs!(model) _add_reserve_eqs!(model)
_add_flexiramp_eqs!(model) # Add system-wide flexiramp requirements
return return
end end
@ -54,3 +55,41 @@ function _add_reserve_eqs!(model::JuMP.Model)::Nothing
end end
return return
end end
function _add_flexiramp_eqs!(model::JuMP.Model)::Nothing
# Note: The flexpramp requirements in Wang & Hobbs (2016) are imposed as hard constraints
# through Eq. (17) and Eq. (18). The constraints eq_min_upflexiramp[t] and eq_min_dwflexiramp[t]
# provided below are modified versions of Eq. (17) and Eq. (18), respectively, in that
# they include slack variables for flexiramp shortfall, which are penalized in the
# objective function.
eq_min_upflexiramp = _init(model, :eq_min_upflexiramp)
eq_min_dwflexiramp = _init(model, :eq_min_dwflexiramp)
instance = model[:instance]
for t in 1:instance.time
flexiramp_shortfall_penalty = instance.flexiramp_shortfall_penalty[t]
# Eq. (17) in Wang & Hobbs (2016)
eq_min_upflexiramp[t] = @constraint(
model,
sum(model[:upflexiramp][g.name, t] for g in instance.units) +
(flexiramp_shortfall_penalty >= 0 ? model[:upflexiramp_shortfall][t] : 0.0) >=
instance.reserves.upflexiramp[t]
)
# Eq. (18) in Wang & Hobbs (2016)
eq_min_dwflexiramp[t] = @constraint(
model,
sum(model[:dwflexiramp][g.name, t] for g in instance.units) +
(flexiramp_shortfall_penalty >= 0 ? model[:dwflexiramp_shortfall][t] : 0.0) >=
instance.reserves.dwflexiramp[t]
)
# Account for flexiramp shortfall contribution to objective
if flexiramp_shortfall_penalty >= 0
add_to_expression!(
model[:obj],
flexiramp_shortfall_penalty,
(model[:upflexiramp_shortfall][t]+model[:dwflexiramp_shortfall][t]),
)
end
end
return
end

@ -13,6 +13,7 @@ function _add_unit!(model::JuMP.Model, g::Unit, formulation::Formulation)
# Variables # Variables
_add_production_vars!(model, g, formulation.prod_vars) _add_production_vars!(model, g, formulation.prod_vars)
_add_reserve_vars!(model, g) _add_reserve_vars!(model, g)
_add_flexiramp_vars!(model, g) # Add variables for flexiramp
_add_startup_shutdown_vars!(model, g) _add_startup_shutdown_vars!(model, g)
_add_status_vars!(model, g, formulation.status_vars) _add_status_vars!(model, g, formulation.status_vars)

@ -50,13 +50,35 @@ function solution(model::JuMP.Model)::OrderedDict
sol["Is on"] = timeseries(model[:is_on], instance.units) sol["Is on"] = timeseries(model[:is_on], instance.units)
sol["Switch on"] = timeseries(model[:switch_on], instance.units) sol["Switch on"] = timeseries(model[:switch_on], instance.units)
sol["Switch off"] = timeseries(model[:switch_off], instance.units) sol["Switch off"] = timeseries(model[:switch_off], instance.units)
sol["Reserve (MW)"] = timeseries(model[:reserve], instance.units) if instance.reserves.upflexiramp != zeros(T) || instance.reserves.dwflexiramp != zeros(T)
sol["Reserve shortfall (MW)"] = OrderedDict( # Report flexiramp solutions only if either of the up-flexiramp and
t => # down-flexiramp requirements is not a default array of zeros
(instance.shortfall_penalty[t] >= 0) ? sol["Up-flexiramp (MW)"] = timeseries(model[:upflexiramp], instance.units)
round(value(model[:reserve_shortfall][t]), digits = 5) : 0.0 for sol["Up-flexiramp shortfall (MW)"] = OrderedDict(
t in 1:instance.time t =>
) (instance.flexiramp_shortfall_penalty[t] >= 0) ?
round(value(model[:upflexiramp_shortfall][t]), digits = 5) : 0.0 for
t in 1:instance.time
)
sol["Down-flexiramp (MW)"] = timeseries(model[:dwflexiramp], instance.units)
sol["Down-flexiramp shortfall (MW)"] = OrderedDict(
t =>
(instance.flexiramp_shortfall_penalty[t] >= 0) ?
round(value(model[:dwflexiramp_shortfall][t]), digits = 5) : 0.0 for
t in 1:instance.time
)
else
# Report spinning reserve solutions only if both up-flexiramp and
# down-flexiramp requirements are arrays of zeros.
sol["Reserve (MW)"] = timeseries(model[:reserve], instance.units)
sol["Reserve shortfall (MW)"] = OrderedDict(
t =>
(instance.shortfall_penalty[t] >= 0) ?
round(value(model[:reserve_shortfall][t]), digits = 5) : 0.0 for
t in 1:instance.time
)
end
sol["Net injection (MW)"] = sol["Net injection (MW)"] =
timeseries(model[:net_injection], instance.buses) timeseries(model[:net_injection], instance.buses)
sol["Load curtail (MW)"] = timeseries(model[:curtail], instance.buses) sol["Load curtail (MW)"] = timeseries(model[:curtail], instance.buses)

@ -338,6 +338,40 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01)
) )
err_count += 1 err_count += 1
end end
upflexiramp =
sum(solution["Up-flexiramp (MW)"][g.name][t] for g in instance.units)
upflexiramp_shortfall =
(instance.flexiramp_shortfall_penalty[t] >= 0) ?
solution["Up-flexiramp shortfall (MW)"][t] : 0
if upflexiramp + upflexiramp_shortfall < instance.reserves.upflexiramp[t] - tol
@error @sprintf(
"Insufficient up-flexiramp at time %d (%.2f + %.2f should be %.2f)",
t,
upflexiramp,
upflexiramp_shortfall,
instance.reserves.upflexiramp[t],
)
err_count += 1
end
dwflexiramp =
sum(solution["Down-flexiramp (MW)"][g.name][t] for g in instance.units)
dwflexiramp_shortfall =
(instance.flexiramp_shortfall_penalty[t] >= 0) ?
solution["Down-flexiramp shortfall (MW)"][t] : 0
if dwflexiramp + dwflexiramp_shortfall < instance.reserves.dwflexiramp[t] - tol
@error @sprintf(
"Insufficient down-flexiramp at time %d (%.2f + %.2f should be %.2f)",
t,
dwflexiramp,
dwflexiramp_shortfall,
instance.reserves.dwflexiramp[t],
)
err_count += 1
end
end end
return err_count return err_count

Loading…
Cancel
Save