Compare commits

...

5 Commits

8 changed files with 107 additions and 30 deletions

View File

@@ -11,6 +11,11 @@ All notable changes to this project will be documented in this file.
[semver]: https://semver.org/spec/v2.0.0.html [semver]: https://semver.org/spec/v2.0.0.html
[pkjjl]: https://pkgdocs.julialang.org/v1/compatibility/#compat-pre-1.0 [pkjjl]: https://pkgdocs.julialang.org/v1/compatibility/#compat-pre-1.0
## [0.2.2] - 2021-07-21
### Fixed
- Fix small bug in validation scripts related to startup costs
- Fix duplicated startup constraints (@mtanneau, #12)
## [0.2.1] - 2021-06-02 ## [0.2.1] - 2021-06-02
### Added ### Added
- Add multiple ramping formulations (ArrCon2000, MorLatRam2013, DamKucRajAta2016, PanGua2016) - Add multiple ramping formulations (ArrCon2000, MorLatRam2013, DamKucRajAta2016, PanGua2016)

View File

@@ -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.1" version = "0.2.2"
[deps] [deps]
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
@@ -31,6 +31,7 @@ julia = "1"
[extras] [extras]
Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76" Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b"
[targets] [targets]
test = ["Cbc", "Test"] test = ["Cbc", "Test", "Gurobi"]

View File

@@ -148,7 +148,7 @@ for g in instance.units
end end
``` ```
### Modifying the model ### Fixing variables, modifying objective function and adding constraints
Since we now have a direct reference to the JuMP decision variables, it is possible to fix variables, change the coefficients in the objective function, or even add new constraints to the model before solving it. The script below shows how can this be accomplished. For more information on modifying an existing model, [see the JuMP documentation](https://jump.dev/JuMP.jl/stable/manual/variables/). Since we now have a direct reference to the JuMP decision variables, it is possible to fix variables, change the coefficients in the objective function, or even add new constraints to the model before solving it. The script below shows how can this be accomplished. For more information on modifying an existing model, [see the JuMP documentation](https://jump.dev/JuMP.jl/stable/manual/variables/).
@@ -190,6 +190,54 @@ JuMP.set_objective_coefficient(
UnitCommitment.optimize!(model) UnitCommitment.optimize!(model)
``` ```
### Adding new component to a bus
The following snippet shows how to add a new grid component to a particular bus. For each time step, we create decision variables for the new grid component, add these variables to the objective function, then attach the component to a particular bus by modifying some existing model constraints.
```julia
using Cbc
using JuMP
using UnitCommitment
# Load instance and build base model
instance = UnitCommitment.read_benchmark("matpower/case118/2017-02-01")
model = UnitCommitment.build_model(
instance=instance,
optimizer=Cbc.Optimizer,
)
# Get the number of time steps in the original instance
T = instance.time
# Create decision variables for the new grid component.
# In this example, we assume that the new component can
# inject up to 10 MW of power at each time step, so we
# create new continuous variables 0 ≤ x[t] ≤ 10.
@variable(model, x[1:T], lower_bound=0.0, upper_bound=10.0)
# For each time step
for t in 1:T
# Add production costs to the objective function.
# In this example, we assume a cost of $5/MW.
set_objective_coefficient(model, x[t], 5.0)
# Attach the new component to bus b1, by modifying the
# constraint `eq_net_injection`.
set_normalized_coefficient(
model[:eq_net_injection]["b1", t],
x[t],
1.0,
)
end
# Solve the model
UnitCommitment.optimize!(model)
# Show optimal values for the x variables
@show value.(x)
```
References References
---------- ----------
* [KnOsWa20] **Bernard Knueven, James Ostrowski and Jean-Paul Watson.** "On Mixed-Integer Programming Formulations for the Unit Commitment Problem". INFORMS Journal on Computing (2020). [DOI: 10.1287/ijoc.2019.0944](https://doi.org/10.1287/ijoc.2019.0944) * [KnOsWa20] **Bernard Knueven, James Ostrowski and Jean-Paul Watson.** "On Mixed-Integer Programming Formulations for the Unit Commitment Problem". INFORMS Journal on Computing (2020). [DOI: 10.1287/ijoc.2019.0944](https://doi.org/10.1287/ijoc.2019.0944)

View File

@@ -12,14 +12,14 @@ function _add_startup_cost_eqs!(
S = length(g.startup_categories) S = length(g.startup_categories)
startup = model[:startup] startup = model[:startup]
for t in 1:model[:instance].time for t in 1:model[:instance].time
for s in 1:S # If unit is switching on, we must choose a startup category
# If unit is switching on, we must choose a startup category eq_startup_choose[g.name, t] = @constraint(
eq_startup_choose[g.name, t, s] = @constraint( model,
model, model[:switch_on][g.name, t] ==
model[:switch_on][g.name, t] == sum(startup[g.name, t, s] for s in 1:S)
sum(startup[g.name, t, s] for s in 1:S) )
)
for s in 1:S
# If unit has not switched off in the last `delay` time periods, startup category is forbidden. # If unit has not switched off in the last `delay` time periods, startup category is forbidden.
# The last startup category is always allowed. # The last startup category is always allowed.
if s < S if s < S

View File

@@ -11,12 +11,12 @@ end
function _add_net_injection_eqs!(model::JuMP.Model)::Nothing function _add_net_injection_eqs!(model::JuMP.Model)::Nothing
T = model[:instance].time T = model[:instance].time
net_injection = _init(model, :net_injection) net_injection = _init(model, :net_injection)
eq_net_injection_def = _init(model, :eq_net_injection_def) eq_net_injection = _init(model, :eq_net_injection)
eq_power_balance = _init(model, :eq_power_balance) eq_power_balance = _init(model, :eq_power_balance)
for t in 1:T, b in model[:instance].buses for t in 1:T, b in model[:instance].buses
n = net_injection[b.name, t] = @variable(model) n = net_injection[b.name, t] = @variable(model)
eq_net_injection_def[t, b.name] = eq_net_injection[b.name, t] =
@constraint(model, n == model[:expr_net_injection][b.name, t]) @constraint(model, -n + model[:expr_net_injection][b.name, t] == 0)
end end
for t in 1:T for t in 1:T
eq_power_balance[t] = @constraint( eq_power_balance[t] = @constraint(

View File

@@ -208,12 +208,8 @@ function _validate_units(instance, solution; tol = 0.01)
break break
end end
end end
if t == time_down + 1 if (t == time_down + 1) && (unit.initial_status < 0)
initial_down = unit.min_downtime time_down -= unit.initial_status
if unit.initial_status < 0
initial_down = -unit.initial_status
end
time_down += initial_down
end end
# Calculate startup costs # Calculate startup costs
@@ -246,14 +242,6 @@ function _validate_units(instance, solution; tol = 0.01)
break break
end end
end end
if t == time_up + 1
initial_up = unit.min_uptime
if unit.initial_status > 0
initial_up = unit.initial_status
end
time_up += initial_up
end
if (t == time_up + 1) && (unit.initial_status > 0) if (t == time_up + 1) && (unit.initial_status > 0)
time_up += unit.initial_status time_up += unit.initial_status
end end

View File

@@ -3,6 +3,7 @@
# Released under the modified BSD license. See COPYING.md for more details. # Released under the modified BSD license. See COPYING.md for more details.
using UnitCommitment using UnitCommitment
using JuMP
import UnitCommitment: import UnitCommitment:
ArrCon2000, ArrCon2000,
CarArr2006, CarArr2006,
@@ -11,17 +12,49 @@ import UnitCommitment:
Gar1962, Gar1962,
KnuOstWat2018, KnuOstWat2018,
MorLatRam2013, MorLatRam2013,
PanGua2016 PanGua2016,
XavQiuWanThi2019
function _test(formulation::Formulation)::Nothing if ENABLE_LARGE_TESTS
using Gurobi
end
function _small_test(formulation::Formulation)::Nothing
instance = UnitCommitment.read_benchmark("matpower/case118/2017-02-01") instance = UnitCommitment.read_benchmark("matpower/case118/2017-02-01")
UnitCommitment.build_model(instance = instance, formulation = formulation) # should not crash UnitCommitment.build_model(instance = instance, formulation = formulation) # should not crash
return return
end end
function _large_test(formulation::Formulation)::Nothing
instances = ["pglib-uc/ca/Scenario400_reserves_1"]
for instance in instances
instance = UnitCommitment.read_benchmark(instance)
model = UnitCommitment.build_model(
instance = instance,
formulation = formulation,
optimizer = Gurobi.Optimizer,
)
UnitCommitment.optimize!(
model,
XavQiuWanThi2019.Method(two_phase_gap = false, gap_limit = 0.1),
)
solution = UnitCommitment.solution(model)
@test UnitCommitment.validate(instance, solution)
end
return
end
function _test(formulation::Formulation)::Nothing
_small_test(formulation)
if ENABLE_LARGE_TESTS
_large_test(formulation)
end
end
@testset "formulations" begin @testset "formulations" begin
_test(Formulation())
_test(Formulation(ramping = ArrCon2000.Ramping())) _test(Formulation(ramping = ArrCon2000.Ramping()))
_test(Formulation(ramping = DamKucRajAta2016.Ramping())) # _test(Formulation(ramping = DamKucRajAta2016.Ramping()))
_test( _test(
Formulation( Formulation(
ramping = MorLatRam2013.Ramping(), ramping = MorLatRam2013.Ramping(),

View File

@@ -7,6 +7,8 @@ using UnitCommitment
UnitCommitment._setup_logger() UnitCommitment._setup_logger()
const ENABLE_LARGE_TESTS = ("UCJL_LARGE_TESTS" in keys(ENV))
@testset "UnitCommitment" begin @testset "UnitCommitment" begin
include("usage.jl") include("usage.jl")
@testset "import" begin @testset "import" begin