Compare commits

..

5 Commits
m2m ... v0.4.2

Author SHA1 Message Date
e4cc95dae1 Bump version to 0.4.2 2025-11-27 09:01:25 -06:00
48094ded6b KnuOstWat2018: Fix eq_segprod_limit when min_uptime=1 2025-11-27 08:58:43 -06:00
4ac9b2a8d5 Bump version to 0.4.1 2025-11-05 09:33:30 -06:00
8763c8d8f7 Bump min julia version to 1.10; disable flaky tests 2025-11-05 09:27:55 -06:00
bbe57f88cd Fix some multi-threading issues
Replace nthreads by maxthreadid and use :static scheduling to disable
task migration. Fixes #56.
2025-11-05 09:09:45 -06:00
12 changed files with 68 additions and 59 deletions

View File

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

View File

@@ -11,6 +11,10 @@ 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 (#17)
## [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.0" version = "0.4.2"
[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" julia = "1.10"
TimerOutputs = "0.5" TimerOutputs = "0.5"

View File

@@ -1,8 +1,8 @@
# Decomposition methods # Decomposition methods
## 1. Time decomposition for production cost modeling ## 1. Time decomposition
Solving unit commitment instances that have long time horizons (for example, year-long 8760-hour instances in production cost modeling) requires a substantial amount of computational power. To address this issue, UC.jl offers a time decomposition method, which breaks the instance down into multiple overlapping subproblems, solves them sequentially, then reassembles the solution. Solving unit commitment instances that have long time horizons (for example, year-long 8760-hour instances) requires a substantial amount of computational power. To address this issue, UC.jl offers a time decomposition method, which breaks the instance down into multiple overlapping subproblems, solves them sequentially, then reassembles the solution.
When solving a unit commitment instance with a dense time slot structure, computational complexity can become a significant challenge. For instance, if the instance contains hourly data for an entire year (8760 hours), solving such a model can require a substantial amount of computational power. To address this issue, UC.jl provides a time_decomposition method within the `optimize!` function. This method decomposes the problem into multiple sub-problems, solving them sequentially. When solving a unit commitment instance with a dense time slot structure, computational complexity can become a significant challenge. For instance, if the instance contains hourly data for an entire year (8760 hours), solving such a model can require a substantial amount of computational power. To address this issue, UC.jl provides a time_decomposition method within the `optimize!` function. This method decomposes the problem into multiple sub-problems, solving them sequentially.
@@ -57,7 +57,7 @@ solution = UnitCommitment.optimize!(
) )
``` ```
## 2. Scenario decomposition with Progressive Hedging for stochstic UC ## 2. Scenario decomposition with Progressive Hedging
By default, UC.jl uses the Extensive Form (EF) when solving stochastic instances. This approach involves constructing a single JuMP model that contains data and decision variables for all scenarios. Although EF has optimality guarantees and performs well with small test cases, it can become computationally intractable for large instances or substantial number of scenarios. By default, UC.jl uses the Extensive Form (EF) when solving stochastic instances. This approach involves constructing a single JuMP model that contains data and decision variables for all scenarios. Although EF has optimality guarantees and performs well with small test cases, it can become computationally intractable for large instances or substantial number of scenarios.

View File

@@ -67,21 +67,19 @@ 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)/(48a) in Kneuven et al. (2020) # Equation (47a) 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)/(48b) in Kneuven et al. (2020) # Equation (47b) 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,67 +26,59 @@ 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 lc === nothing if violation.outage_line === nothing
limit = lm.normal_flow_limit[t] limit = violation.monitored_line.normal_flow_limit[violation.time]
@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,
lm.name, violation.monitored_line.name,
t, violation.time,
sc.name, sc.name,
) )
else else
limit = lm.emergency_flow_limit[t] limit = violation.monitored_line.emergency_flow_limit[violation.time]
@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,
lm.name, violation.monitored_line.name,
t, violation.time,
lc.name, violation.outage_line.name,
sc.name, sc.name,
) )
end end
v = overflow[sc.name, lm.name, t] fm = violation.monitored_line.name
flow = @variable(model, base_name = "flow[$eq_idx]") t = violation.time
eq_flow_ub[eq_idx] = @constraint(model, flow <= limit + v) flow = @variable(model, base_name = "flow[$fm,$t]")
eq_flow_lb[eq_idx] = @constraint(model, -flow <= limit + v)
if lc === nothing v = overflow[sc.name, violation.monitored_line.name, violation.time]
eq_flow_def[eq_idx] = @constraint( @constraint(model, flow <= limit + v)
@constraint(model, -flow <= limit + v)
if violation.outage_line === nothing
@constraint(
model, model,
flow == sum( flow == sum(
net_injection[sc.name, b.name, t] * net_injection[sc.name, b.name, violation.time] *
isf[lm.offset, b.offset] for isf[violation.monitored_line.offset, b.offset] for
b in sc.buses if b.offset > 0 b in sc.buses if b.offset > 0
) )
) )
else else
eq_flow_def[eq_idx] = @constraint( @constraint(
model, model,
flow == sum( flow == sum(
net_injection[sc.name, b.name, t] * ( net_injection[sc.name, b.name, violation.time] * (
isf[lm.offset, b.offset] + ( isf[violation.monitored_line.offset, b.offset] + (
lodf[ lodf[
lm.offset, violation.monitored_line.offset,
lc.offset, violation.outage_line.offset,
] * isf[lc.offset, b.offset] ] * isf[violation.outage_line.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 import Base.Threads: @threads, maxthreadid
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 = nthreads() K = maxthreadid()
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 for t in 1:T @threads :static for t in 1:T
k = threadid() k = threadid()
# Pre-contingency flows # Pre-contingency flows

View File

@@ -2,9 +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.
using DataStructures function optimize!(model::JuMP.Model, method::XavQiuWanThi2019.Method)::Nothing
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
@@ -24,9 +22,6 @@ function optimize!(model::JuMP.Model, method::XavQiuWanThi2019.Method)::Dict
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
@@ -73,7 +68,6 @@ function optimize!(model::JuMP.Model, method::XavQiuWanThi2019.Method)::Dict
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
@@ -86,5 +80,5 @@ function optimize!(model::JuMP.Model, method::XavQiuWanThi2019.Method)::Dict
end end
end end
end end
return stats return
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)::Dict optimize!(model::JuMP.Model)::Nothing
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)::Dict function optimize!(model::JuMP.Model)::Nothing
return UnitCommitment.optimize!(model, XavQiuWanThi2019.Method()) return UnitCommitment.optimize!(model, XavQiuWanThi2019.Method())
end end

BIN
test/fixtures/issue-0057.json.gz vendored Normal file

Binary file not shown.

View File

@@ -23,6 +23,7 @@ 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__)
@@ -48,12 +49,13 @@ 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

19
test/src/regression.jl Normal file
View File

@@ -0,0 +1,19 @@
# 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