mirror of
https://github.com/ANL-CEEESA/UnitCommitment.jl.git
synced 2025-12-06 08:18:51 -06:00
Compare commits
15 Commits
m2m
...
feature/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b429bc664 | ||
|
|
2d48c84f1a | ||
|
|
718d6af96b | ||
|
|
56c9e28495 | ||
|
|
3d252c55a3 | ||
|
|
f44d7bcfdf | ||
| c64b76d6d1 | |||
| f514ace560 | |||
|
|
97b8611fcc | ||
| 209c3a72e9 | |||
| fe3066f2b5 | |||
|
|
92221bcaa4 | ||
|
|
2cdf8874fb | ||
|
|
ea35c3ffcc | ||
|
|
7a03f4bbb0 |
@@ -2,7 +2,7 @@ name = "UnitCommitment"
|
|||||||
uuid = "64606440-39ea-11e9-0f29-3303a1d3d877"
|
uuid = "64606440-39ea-11e9-0f29-3303a1d3d877"
|
||||||
authors = ["Santos Xavier, Alinson <axavier@anl.gov>"]
|
authors = ["Santos Xavier, Alinson <axavier@anl.gov>"]
|
||||||
repo = "https://github.com/ANL-CEEESA/UnitCommitment.jl"
|
repo = "https://github.com/ANL-CEEESA/UnitCommitment.jl"
|
||||||
version = "0.2.2"
|
version = "0.2.3"
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
|
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
|
||||||
|
|||||||
@@ -28,13 +28,14 @@ Each section is described in detail below. For a complete example, see [case14](
|
|||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
This section describes system-wide parameters, such as power balance penalties, optimization parameters, such as the length of the planning horizon and the time.
|
This section describes system-wide parameters, such as power balance and reserve shortfall penalties, and optimization parameters, such as the length of the planning horizon and the time.
|
||||||
|
|
||||||
| Key | Description | Default | Time series?
|
| Key | Description | Default | Time series?
|
||||||
| :----------------------------- | :------------------------------------------------ | :------: | :------------:
|
| :----------------------------- | :------------------------------------------------ | :------: | :------------:
|
||||||
| `Time horizon (h)` | Length of the planning horizon (in hours). | Required | N
|
| `Time horizon (h)` | Length of the planning horizon (in hours). | Required | 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
|
| `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
|
||||||
|
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
@@ -42,7 +43,8 @@ This section describes system-wide parameters, such as power balance penalties,
|
|||||||
{
|
{
|
||||||
"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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
Binary file not shown.
@@ -98,6 +98,10 @@ 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],
|
||||||
)
|
)
|
||||||
|
shortfall_penalty = timeseries(
|
||||||
|
json["Parameters"]["Reserve shortfall penalty (\$/MW)"],
|
||||||
|
default = [-1.0 for t in 1:T],
|
||||||
|
)
|
||||||
|
|
||||||
# Read buses
|
# Read buses
|
||||||
for (bus_name, dict) in json["Buses"]
|
for (bus_name, dict) in json["Buses"]
|
||||||
@@ -264,6 +268,7 @@ function _from_json(json; repair = true)
|
|||||||
instance = UnitCommitmentInstance(
|
instance = UnitCommitmentInstance(
|
||||||
T,
|
T,
|
||||||
power_balance_penalty,
|
power_balance_penalty,
|
||||||
|
shortfall_penalty,
|
||||||
units,
|
units,
|
||||||
buses,
|
buses,
|
||||||
lines,
|
lines,
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ end
|
|||||||
mutable struct UnitCommitmentInstance
|
mutable struct UnitCommitmentInstance
|
||||||
time::Int
|
time::Int
|
||||||
power_balance_penalty::Vector{Float64}
|
power_balance_penalty::Vector{Float64}
|
||||||
|
"Penalty for failing to meet reserve requirement."
|
||||||
|
shortfall_penalty::Vector{Float64}
|
||||||
units::Vector{Unit}
|
units::Vector{Unit}
|
||||||
buses::Vector{Bus}
|
buses::Vector{Bus}
|
||||||
lines::Vector{TransmissionLine}
|
lines::Vector{TransmissionLine}
|
||||||
|
|||||||
@@ -4,15 +4,11 @@
|
|||||||
|
|
||||||
function _add_bus!(model::JuMP.Model, b::Bus)::Nothing
|
function _add_bus!(model::JuMP.Model, b::Bus)::Nothing
|
||||||
net_injection = _init(model, :expr_net_injection)
|
net_injection = _init(model, :expr_net_injection)
|
||||||
reserve = _init(model, :expr_reserve)
|
|
||||||
curtail = _init(model, :curtail)
|
curtail = _init(model, :curtail)
|
||||||
for t in 1:model[:instance].time
|
for t in 1:model[:instance].time
|
||||||
# Fixed load
|
# Fixed load
|
||||||
net_injection[b.name, t] = AffExpr(-b.load[t])
|
net_injection[b.name, t] = AffExpr(-b.load[t])
|
||||||
|
|
||||||
# Reserves
|
|
||||||
reserve[b.name, t] = AffExpr()
|
|
||||||
|
|
||||||
# Load curtailment
|
# Load curtailment
|
||||||
curtail[b.name, t] =
|
curtail[b.name, t] =
|
||||||
@variable(model, lower_bound = 0, upper_bound = b.load[t])
|
@variable(model, lower_bound = 0, upper_bound = b.load[t])
|
||||||
|
|||||||
@@ -29,13 +29,28 @@ end
|
|||||||
|
|
||||||
function _add_reserve_eqs!(model::JuMP.Model)::Nothing
|
function _add_reserve_eqs!(model::JuMP.Model)::Nothing
|
||||||
eq_min_reserve = _init(model, :eq_min_reserve)
|
eq_min_reserve = _init(model, :eq_min_reserve)
|
||||||
for t in 1:model[:instance].time
|
instance = model[:instance]
|
||||||
|
for t in 1:instance.time
|
||||||
|
# Equation (68) in Kneuven et al. (2020)
|
||||||
|
# As in Morales-España et al. (2013a)
|
||||||
|
# Akin to the alternative formulation with max_power_avail
|
||||||
|
# from Carrión and Arroyo (2006) and Ostrowski et al. (2012)
|
||||||
|
shortfall_penalty = instance.shortfall_penalty[t]
|
||||||
eq_min_reserve[t] = @constraint(
|
eq_min_reserve[t] = @constraint(
|
||||||
model,
|
model,
|
||||||
sum(
|
sum(model[:reserve][g.name, t] for g in instance.units) +
|
||||||
model[:expr_reserve][b.name, t] for b in model[:instance].buses
|
(shortfall_penalty >= 0 ? model[:reserve_shortfall][t] : 0.0) >=
|
||||||
) >= model[:instance].reserves.spinning[t]
|
instance.reserves.spinning[t]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Account for shortfall contribution to objective
|
||||||
|
if shortfall_penalty >= 0
|
||||||
|
add_to_expression!(
|
||||||
|
model[:obj],
|
||||||
|
shortfall_penalty,
|
||||||
|
model[:reserve_shortfall][t],
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -44,12 +44,16 @@ _is_initially_on(g::Unit)::Float64 = (g.initial_status > 0 ? 1.0 : 0.0)
|
|||||||
|
|
||||||
function _add_reserve_vars!(model::JuMP.Model, g::Unit)::Nothing
|
function _add_reserve_vars!(model::JuMP.Model, g::Unit)::Nothing
|
||||||
reserve = _init(model, :reserve)
|
reserve = _init(model, :reserve)
|
||||||
|
reserve_shortfall = _init(model, :reserve_shortfall)
|
||||||
for t in 1:model[:instance].time
|
for t in 1:model[:instance].time
|
||||||
if g.provides_spinning_reserves[t]
|
if g.provides_spinning_reserves[t]
|
||||||
reserve[g.name, t] = @variable(model, lower_bound = 0)
|
reserve[g.name, t] = @variable(model, lower_bound = 0)
|
||||||
else
|
else
|
||||||
reserve[g.name, t] = 0.0
|
reserve[g.name, t] = 0.0
|
||||||
end
|
end
|
||||||
|
reserve_shortfall[t] =
|
||||||
|
(model[:instance].shortfall_penalty[t] >= 0) ?
|
||||||
|
@variable(model, lower_bound = 0) : 0.0
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -210,11 +214,5 @@ function _add_net_injection_eqs!(model::JuMP.Model, g::Unit)::Nothing
|
|||||||
model[:is_on][g.name, t],
|
model[:is_on][g.name, t],
|
||||||
g.min_power[t],
|
g.min_power[t],
|
||||||
)
|
)
|
||||||
# Add to reserves expression
|
|
||||||
add_to_expression!(
|
|
||||||
model[:expr_reserve][g.bus.name, t],
|
|
||||||
model[:reserve][g.name, t],
|
|
||||||
1.0,
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -51,6 +51,12 @@ function solution(model::JuMP.Model)::OrderedDict
|
|||||||
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)
|
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
|
||||||
|
)
|
||||||
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)
|
||||||
|
|||||||
@@ -324,11 +324,16 @@ function _validate_reserve_and_demand(instance, solution, tol = 0.01)
|
|||||||
# Verify spinning reserves
|
# Verify spinning reserves
|
||||||
reserve =
|
reserve =
|
||||||
sum(solution["Reserve (MW)"][g.name][t] for g in instance.units)
|
sum(solution["Reserve (MW)"][g.name][t] for g in instance.units)
|
||||||
if reserve < instance.reserves.spinning[t] - tol
|
reserve_shortfall =
|
||||||
|
(instance.shortfall_penalty[t] >= 0) ?
|
||||||
|
solution["Reserve shortfall (MW)"][t] : 0
|
||||||
|
|
||||||
|
if reserve + reserve_shortfall < instance.reserves.spinning[t] - tol
|
||||||
@error @sprintf(
|
@error @sprintf(
|
||||||
"Insufficient spinning reserves at time %d (%.2f should be %.2f)",
|
"Insufficient spinning reserves at time %d (%.2f + %.2f should be %.2f)",
|
||||||
t,
|
t,
|
||||||
reserve,
|
reserve,
|
||||||
|
reserve_shortfall,
|
||||||
instance.reserves.spinning[t],
|
instance.reserves.spinning[t],
|
||||||
)
|
)
|
||||||
err_count += 1
|
err_count += 1
|
||||||
|
|||||||
@@ -20,8 +20,14 @@ if ENABLE_LARGE_TESTS
|
|||||||
end
|
end
|
||||||
|
|
||||||
function _small_test(formulation::Formulation)::Nothing
|
function _small_test(formulation::Formulation)::Nothing
|
||||||
instance = UnitCommitment.read_benchmark("matpower/case118/2017-02-01")
|
instances = ["matpower/case118/2017-02-01", "test/case14"]
|
||||||
UnitCommitment.build_model(instance = instance, formulation = formulation) # should not crash
|
for instance in instances
|
||||||
|
# Should not crash
|
||||||
|
UnitCommitment.build_model(
|
||||||
|
instance = UnitCommitment.read_benchmark(instance),
|
||||||
|
formulation = formulation,
|
||||||
|
)
|
||||||
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user