Compare commits

..

2 Commits

15 changed files with 63 additions and 80 deletions

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
version: ['1.10', '1.12'] version: ['1.6', '1.7', '1.8', '1.9']
os: os:
- ubuntu-latest - ubuntu-latest
arch: arch:

11
.gitignore vendored
View File

@@ -1,4 +1,3 @@
*-off.md
*.bak *.bak
*.gz *.gz
*.ipynb *.ipynb
@@ -20,7 +19,6 @@
.apdisk .apdisk
.com.apple.timemachine.donotpresent .com.apple.timemachine.donotpresent
.fseventsd .fseventsd
.idea
.ipy* .ipy*
.vscode .vscode
Icon Icon
@@ -34,11 +32,12 @@ benchmark/tables
benchmark/tmp.json benchmark/tmp.json
build build
docs/_build docs/_build
docs/src/tutorials/customizing.md
docs/src/tutorials/lmp.md
docs/src/tutorials/market.md
docs/src/tutorials/usage.md
instances/**/*.json instances/**/*.json
instances/_source instances/_source
local local
notebooks notebooks
docs/src/tutorials/usage.md
docs/src/tutorials/customizing.md
docs/src/tutorials/market.md
docs/src/tutorials/lmp.md
*-off.md

View File

@@ -11,17 +11,6 @@ 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.4.2] - 2025-11-27
### Fixed
- KnuOstWat2018: Fixed a bug in `eq_segprod_limit` constraint (#57)
## [0.4.1] - 2025-11-05
### Fixed
- Fix multi-threading issues in Julia 1.12
### Changed
- The package now requires Julia 1.10 or newer
## [0.4.0] - 2024-05-21 ## [0.4.0] - 2024-05-21
### Added ### Added
- Add support for two-stage stochastic problems - Add support for two-stage stochastic problems

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.4.2" version = "0.4.0"
[deps] [deps]
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
@@ -30,5 +30,5 @@ JuMP = "1"
MathOptInterface = "1" MathOptInterface = "1"
MPI = "0.20" MPI = "0.20"
PackageCompiler = "1" PackageCompiler = "1"
julia = "1.10" julia = "1"
TimerOutputs = "0.5" TimerOutputs = "0.5"

View File

@@ -108,7 +108,7 @@ See official documentation at: https://anl-ceeesa.github.io/UnitCommitment.jl/
If you use UnitCommitment.jl in your research (instances, models or algorithms), we kindly request that you cite the package as follows: If you use UnitCommitment.jl in your research (instances, models or algorithms), we kindly request that you cite the package as follows:
* **Alinson S. Xavier, Aleksandr M. Kazachkov, Ogün Yurdakul, Jun He, Feng Qiu**. "UnitCommitment.jl: A Julia/JuMP Optimization Package for Security-Constrained Unit Commitment (Version 0.4)". Zenodo (2024). [DOI: 10.5281/zenodo.4269874](https://doi.org/10.5281/zenodo.4269874). * **Alinson S. Xavier, Aleksandr M. Kazachkov, Ogün Yurdakul, Feng Qiu**. "UnitCommitment.jl: A Julia/JuMP Optimization Package for Security-Constrained Unit Commitment (Version 0.4)". Zenodo (2024). [DOI: 10.5281/zenodo.4269874](https://doi.org/10.5281/zenodo.4269874).
If you use the instances, we additionally request that you cite the original sources, as described in the documentation. If you use the instances, we additionally request that you cite the original sources, as described in the documentation.

View File

@@ -107,7 +107,7 @@ Note that this curve also specifies the production limits. Specifically, the fir
```@raw html ```@raw html
<center> <center>
<img src="../../assets/cost_curve.png" style="max-width: 500px"/> <img src="../assets/cost_curve.png" style="max-width: 500px"/>
<div><b>Figure 1.</b> Piecewise-linear production cost curve.</div> <div><b>Figure 1.</b> Piecewise-linear production cost curve.</div>
<br/> <br/>
</center> </center>

View File

@@ -67,19 +67,21 @@ function _add_production_piecewise_linear_eqs!(
(t < T ? Cw * switch_off[gn, t+1] : 0.0) (t < T ? Cw * switch_off[gn, t+1] : 0.0)
) )
else else
# Equation (47a) in Kneuven et al. (2020) # Equation (47a)/(48a) in Kneuven et al. (2020)
eq_segprod_limit_b[sc.name, gn, t, k] = @constraint( eq_segprod_limit_b[sc.name, gn, t, k] = @constraint(
model, model,
segprod[sc.name, gn, t, k] <= segprod[sc.name, gn, t, k] <=
g.cost_segments[k].mw[t] * is_on[gn, t] - g.cost_segments[k].mw[t] * is_on[gn, t] -
Cv * switch_on[gn, t] Cv * switch_on[gn, t] -
(t < T ? max(0, Cv - Cw) * switch_off[gn, t+1] : 0.0)
) )
# Equation (47b) in Kneuven et al. (2020) # Equation (47b)/(48b) in Kneuven et al. (2020)
eq_segprod_limit_c[sc.name, gn, t, k] = @constraint( eq_segprod_limit_c[sc.name, gn, t, k] = @constraint(
model, model,
segprod[sc.name, gn, t, k] <= segprod[sc.name, gn, t, k] <=
g.cost_segments[k].mw[t] * is_on[gn, t] - g.cost_segments[k].mw[t] * is_on[gn, t] -
max(0, Cw - Cv) * switch_on[gn, t] -
(t < T ? Cw * switch_off[gn, t+1] : 0.0) (t < T ? Cw * switch_off[gn, t+1] : 0.0)
) )
end end

View File

@@ -26,59 +26,67 @@ function _enforce_transmission(;
isf::Matrix{Float64}, isf::Matrix{Float64},
lodf::Matrix{Float64}, lodf::Matrix{Float64},
)::Nothing )::Nothing
instance = model[:instance]
limit::Float64 = 0.0 limit::Float64 = 0.0
overflow = model[:overflow] overflow = model[:overflow]
net_injection = model[:net_injection] net_injection = model[:net_injection]
lm = violation.monitored_line
lc = violation.outage_line
t = violation.time
eq_flow_ub = _init(model, :eq_flow_ub)
eq_flow_lb = _init(model, :eq_flow_lb)
eq_flow_def = _init(model, :eq_flow_def)
eq_idx = (
sc.name,
lm.name,
lc === nothing ? "Base" : lc.name,
t,
)
if violation.outage_line === nothing if lc === nothing
limit = violation.monitored_line.normal_flow_limit[violation.time] limit = lm.normal_flow_limit[t]
@info @sprintf( @info @sprintf(
" %8.3f MW overflow in %-5s time %3d (pre-contingency, scenario %s)", " %8.3f MW overflow in %-5s time %3d (pre-contingency, scenario %s)",
violation.amount, violation.amount,
violation.monitored_line.name, lm.name,
violation.time, t,
sc.name, sc.name,
) )
else else
limit = violation.monitored_line.emergency_flow_limit[violation.time] limit = lm.emergency_flow_limit[t]
@info @sprintf( @info @sprintf(
" %8.3f MW overflow in %-5s time %3d (outage: line %s, scenario %s)", " %8.3f MW overflow in %-5s time %3d (outage: line %s, scenario %s)",
violation.amount, violation.amount,
violation.monitored_line.name, lm.name,
violation.time, t,
violation.outage_line.name, lc.name,
sc.name, sc.name,
) )
end end
fm = violation.monitored_line.name v = overflow[sc.name, lm.name, t]
t = violation.time flow = @variable(model, base_name = "flow[$eq_idx]")
flow = @variable(model, base_name = "flow[$fm,$t]") eq_flow_ub[eq_idx] = @constraint(model, flow <= limit + v)
eq_flow_lb[eq_idx] = @constraint(model, -flow <= limit + v)
v = overflow[sc.name, violation.monitored_line.name, violation.time] if lc === nothing
@constraint(model, flow <= limit + v) eq_flow_def[eq_idx] = @constraint(
@constraint(model, -flow <= limit + v)
if violation.outage_line === nothing
@constraint(
model, model,
flow == sum( flow == sum(
net_injection[sc.name, b.name, violation.time] * net_injection[sc.name, b.name, t] *
isf[violation.monitored_line.offset, b.offset] for isf[lm.offset, b.offset] for
b in sc.buses if b.offset > 0 b in sc.buses if b.offset > 0
) )
) )
else else
@constraint( eq_flow_def[eq_idx] = @constraint(
model, model,
flow == sum( flow == sum(
net_injection[sc.name, b.name, violation.time] * ( net_injection[sc.name, b.name, t] * (
isf[violation.monitored_line.offset, b.offset] + ( isf[lm.offset, b.offset] + (
lodf[ lodf[
violation.monitored_line.offset, lm.offset,
violation.outage_line.offset, lc.offset,
] * isf[violation.outage_line.offset, b.offset] ] * isf[lc.offset, b.offset]
) )
) for b in sc.buses if b.offset > 0 ) for b in sc.buses if b.offset > 0
) )

View File

@@ -2,7 +2,7 @@
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. # Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details. # Released under the modified BSD license. See COPYING.md for more details.
import Base.Threads: @threads, maxthreadid import Base.Threads: @threads
function _find_violations( function _find_violations(
model::JuMP.Model, model::JuMP.Model,
@@ -71,7 +71,7 @@ function _find_violations(;
B = length(sc.buses) - 1 B = length(sc.buses) - 1
L = length(sc.lines) L = length(sc.lines)
T = instance.time T = instance.time
K = maxthreadid() K = nthreads()
size(net_injections) == (B, T) || error("net_injections has incorrect size") size(net_injections) == (B, T) || error("net_injections has incorrect size")
size(isf) == (L, B) || error("isf has incorrect size") size(isf) == (L, B) || error("isf has incorrect size")
@@ -104,7 +104,7 @@ function _find_violations(;
is_vulnerable[c.lines[1].offset] = true is_vulnerable[c.lines[1].offset] = true
end end
@threads :static for t in 1:T @threads for t in 1:T
k = threadid() k = threadid()
# Pre-contingency flows # Pre-contingency flows

View File

@@ -2,7 +2,9 @@
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. # Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details. # Released under the modified BSD license. See COPYING.md for more details.
function optimize!(model::JuMP.Model, method::XavQiuWanThi2019.Method)::Nothing using DataStructures
function optimize!(model::JuMP.Model, method::XavQiuWanThi2019.Method)::Dict
if !occursin("Gurobi", JuMP.solver_name(model)) if !occursin("Gurobi", JuMP.solver_name(model))
method.two_phase_gap = false method.two_phase_gap = false
end end
@@ -22,6 +24,9 @@ function optimize!(model::JuMP.Model, method::XavQiuWanThi2019.Method)::Nothing
large_gap = true large_gap = true
end end
end end
stats = Dict(
"violations" => []
)
while true while true
time_elapsed = time() - initial_time time_elapsed = time() - initial_time
time_remaining = method.time_limit - time_elapsed time_remaining = method.time_limit - time_elapsed
@@ -68,6 +73,7 @@ function optimize!(model::JuMP.Model, method::XavQiuWanThi2019.Method)::Nothing
if violations_found if violations_found
for (i, v) in enumerate(violations) for (i, v) in enumerate(violations)
append!(stats["violations"], v)
_enforce_transmission(model, v, model[:instance].scenarios[i]) _enforce_transmission(model, v, model[:instance].scenarios[i])
end end
else else
@@ -80,5 +86,5 @@ function optimize!(model::JuMP.Model, method::XavQiuWanThi2019.Method)::Nothing
end end
end end
end end
return return stats
end end

View File

@@ -3,12 +3,12 @@
# Released under the modified BSD license. See COPYING.md for more details. # Released under the modified BSD license. See COPYING.md for more details.
""" """
optimize!(model::JuMP.Model)::Nothing optimize!(model::JuMP.Model)::Dict
Solve the given unit commitment model. Unlike `JuMP.optimize!`, this uses more Solve the given unit commitment model. Unlike `JuMP.optimize!`, this uses more
advanced methods to accelerate the solution process and to enforce transmission advanced methods to accelerate the solution process and to enforce transmission
and N-1 security constraints. and N-1 security constraints.
""" """
function optimize!(model::JuMP.Model)::Nothing function optimize!(model::JuMP.Model)::Dict
return UnitCommitment.optimize!(model, XavQiuWanThi2019.Method()) return UnitCommitment.optimize!(model, XavQiuWanThi2019.Method())
end end

Binary file not shown.

Binary file not shown.

View File

@@ -23,7 +23,6 @@ include("validation/repair_test.jl")
include("lmp/conventional_test.jl") include("lmp/conventional_test.jl")
include("lmp/aelmp_test.jl") include("lmp/aelmp_test.jl")
include("market/market_test.jl") include("market/market_test.jl")
include("regression.jl")
basedir = dirname(@__FILE__) basedir = dirname(@__FILE__)
@@ -49,13 +48,12 @@ function runtests()
solution_methods_TimeDecomposition_update_solution_test() solution_methods_TimeDecomposition_update_solution_test()
transform_initcond_test() transform_initcond_test()
transform_slice_test() transform_slice_test()
# transform_randomize_XavQiuAhm2021_test() transform_randomize_XavQiuAhm2021_test()
validation_repair_test() validation_repair_test()
lmp_conventional_test() lmp_conventional_test()
lmp_aelmp_test() lmp_aelmp_test()
simple_market_test() simple_market_test()
stochastic_market_test() stochastic_market_test()
regression_test()
end end
return return
end end

View File

@@ -1,19 +0,0 @@
# UnitCommitment.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.
using UnitCommitment, HiGHS, JuMP
function regression_test()
@testset "GitHub Issue #57" begin
instance = UnitCommitment.read(fixture("issue-0057.json.gz"))
model = UnitCommitment.build_model(
instance = instance,
optimizer = HiGHS.Optimizer,
)
JuMP.set_silent(model)
UnitCommitment.optimize!(model)
solution = UnitCommitment.solution(model)
@test solution["Thermal production (MW)"]["gen_524d4c85"][1] == 90.0
end
end