diff --git a/src/solution/methods/XavQiuWanThi2019/enforce.jl b/src/solution/methods/XavQiuWanThi2019/enforce.jl index 8c5f3b0..87b39f0 100644 --- a/src/solution/methods/XavQiuWanThi2019/enforce.jl +++ b/src/solution/methods/XavQiuWanThi2019/enforce.jl @@ -34,19 +34,21 @@ function _enforce_transmission(; if violation.outage_line === nothing limit = violation.monitored_line.normal_flow_limit[violation.time] @info @sprintf( - " %8.3f MW overflow in %-5s time %3d (pre-contingency)", + " %8.3f MW overflow in %-5s time %3d (pre-contingency, scenario %s)", violation.amount, violation.monitored_line.name, violation.time, + sc.name, ) else limit = violation.monitored_line.emergency_flow_limit[violation.time] @info @sprintf( - " %8.3f MW overflow in %-5s time %3d (outage: line %s)", + " %8.3f MW overflow in %-5s time %3d (outage: line %s, scenario %s)", violation.amount, violation.monitored_line.name, violation.time, violation.outage_line.name, + sc.name, ) end diff --git a/src/solution/methods/XavQiuWanThi2019/find.jl b/src/solution/methods/XavQiuWanThi2019/find.jl index b9d97e6..406989f 100644 --- a/src/solution/methods/XavQiuWanThi2019/find.jl +++ b/src/solution/methods/XavQiuWanThi2019/find.jl @@ -15,31 +15,25 @@ function _find_violations( overflow = model[:overflow] length(sc.buses) > 1 || return [] violations = [] - @info "Verifying transmission limits..." - time_screening = @elapsed begin - non_slack_buses = [b for b in sc.buses if b.offset > 0] - net_injection_values = [ - value(net_injection[sc.name, b.name, t]) for - b in non_slack_buses, t in 1:instance.time - ] - overflow_values = [ - value(overflow[sc.name, lm.name, t]) for lm in sc.lines, - t in 1:instance.time - ] - violations = UnitCommitment._find_violations( - instance = instance, - sc = sc, - net_injections = net_injection_values, - overflow = overflow_values, - isf = sc.isf, - lodf = sc.lodf, - max_per_line = max_per_line, - max_per_period = max_per_period, - ) - end - @info @sprintf( - "Verified transmission limits in %.2f seconds", - time_screening + + non_slack_buses = [b for b in sc.buses if b.offset > 0] + net_injection_values = [ + value(net_injection[sc.name, b.name, t]) for b in non_slack_buses, + t in 1:instance.time + ] + overflow_values = [ + value(overflow[sc.name, lm.name, t]) for lm in sc.lines, + t in 1:instance.time + ] + violations = UnitCommitment._find_violations( + instance = instance, + sc = sc, + net_injections = net_injection_values, + overflow = overflow_values, + isf = sc.isf, + lodf = sc.lodf, + max_per_line = max_per_line, + max_per_period = max_per_period, ) return violations end diff --git a/src/solution/methods/XavQiuWanThi2019/optimize.jl b/src/solution/methods/XavQiuWanThi2019/optimize.jl index 14a34d2..57a202e 100644 --- a/src/solution/methods/XavQiuWanThi2019/optimize.jl +++ b/src/solution/methods/XavQiuWanThi2019/optimize.jl @@ -10,46 +10,73 @@ function optimize!(model::JuMP.Model, method::XavQiuWanThi2019.Method)::Nothing JuMP.set_optimizer_attribute(model, "MIPGap", gap) @info @sprintf("MIP gap tolerance set to %f", gap) end + initial_time = time() + large_gap = false + has_transmission = false for sc in model[:instance].scenarios - large_gap = false - has_transmission = (length(sc.isf) > 0) + if length(sc.isf) > 0 + has_transmission = true + end if has_transmission && method.two_phase_gap set_gap(1e-2) large_gap = true end + end + while true + time_elapsed = time() - initial_time + time_remaining = method.time_limit - time_elapsed + if time_remaining < 0 + @info "Time limit exceeded" + break + end + @info @sprintf( + "Setting MILP time limit to %.2f seconds", + time_remaining + ) + JuMP.set_time_limit_sec(model, time_remaining) + @info "Solving MILP..." JuMP.optimize!(model) - while true - initial_time = time() - time_elapsed = time() - initial_time - time_remaining = method.time_limit - time_elapsed - if time_remaining < 0 - @info "Time limit exceeded" - break + + has_transmission || break + + @info "Verifying transmission limits..." + time_screening = @elapsed begin + violations = [] + for sc in model[:instance].scenarios + push!( + violations, + _find_violations( + model, + sc, + max_per_line = method.max_violations_per_line, + max_per_period = method.max_violations_per_period, + ), + ) end - @info @sprintf( - "Setting MILP time limit to %.2f seconds", - time_remaining - ) - JuMP.set_time_limit_sec(model, time_remaining) - @info "Solving MILP..." - JuMP.optimize!(model) - has_transmission || break - violations = _find_violations( - model, - sc, - max_per_line = method.max_violations_per_line, - max_per_period = method.max_violations_per_period, - ) - if isempty(violations) - @info "No violations found" - if large_gap - large_gap = false - set_gap(method.gap_limit) - else - break - end + end + @info @sprintf( + "Verified transmission limits in %.2f seconds", + time_screening + ) + + violations_found = false + for v in violations + if !isempty(v) + violations_found = true + end + end + + if violations_found + for (i, v) in enumerate(violations) + _enforce_transmission(model, v, model[:instance].scenarios[i]) + end + else + @info "No violations found" + if large_gap + large_gap = false + set_gap(method.gap_limit) else - _enforce_transmission(model, violations, sc) + break end end end diff --git a/test/usage.jl b/test/usage.jl index 74e0719..63b3f36 100644 --- a/test/usage.jl +++ b/test/usage.jl @@ -4,37 +4,59 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON -@testset "usage" begin - instance = UnitCommitment.read("$FIXTURES/case14.json.gz") +function _set_flow_limits!(instance) for sc in instance.scenarios sc.power_balance_penalty = [100_000 for _ in 1:instance.time] for line in sc.lines, t in 1:4 line.normal_flow_limit[t] = 10.0 end end - optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) - model = UnitCommitment.build_model( - instance = instance, - optimizer = optimizer, - variable_names = true, - ) - @test name(model[:is_on]["g1", 1]) == "is_on[g1,1]" - - # Optimize and retrieve solution - UnitCommitment.optimize!(model) - solution = UnitCommitment.solution(model) - - # Write solution to a file - filename = tempname() - UnitCommitment.write(filename, solution) - loaded = JSON.parsefile(filename) - @test length(loaded["Is on"]) == 6 - - # Verify solution - @test UnitCommitment.validate(instance, solution) - - # Reoptimize with fixed solution - UnitCommitment.fix!(model, solution) - UnitCommitment.optimize!(model) - @test UnitCommitment.validate(instance, solution) +end + +@testset "usage" begin + @testset "deterministic" begin + instance = UnitCommitment.read("$FIXTURES/case14.json.gz") + _set_flow_limits!(instance) + optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) + model = UnitCommitment.build_model( + instance = instance, + optimizer = optimizer, + variable_names = true, + ) + @test name(model[:is_on]["g1", 1]) == "is_on[g1,1]" + + # Optimize and retrieve solution + UnitCommitment.optimize!(model) + solution = UnitCommitment.solution(model) + + # Write solution to a file + filename = tempname() + UnitCommitment.write(filename, solution) + loaded = JSON.parsefile(filename) + @test length(loaded["Is on"]) == 6 + + # Verify solution + @test UnitCommitment.validate(instance, solution) + + # Reoptimize with fixed solution + UnitCommitment.fix!(model, solution) + UnitCommitment.optimize!(model) + @test UnitCommitment.validate(instance, solution) + end + + @testset "stochastic" begin + instance = UnitCommitment.read([ + "$FIXTURES/case14.json.gz", + "$FIXTURES/case14.json.gz", + ]) + _set_flow_limits!(instance) + @test length(instance.scenarios) == 2 + optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) + model = UnitCommitment.build_model( + instance = instance, + optimizer = optimizer, + ) + UnitCommitment.optimize!(model) + solution = UnitCommitment.solution(model) + end end