diff --git a/src/UnitCommitment.jl b/src/UnitCommitment.jl index d46d71e..2a85320 100644 --- a/src/UnitCommitment.jl +++ b/src/UnitCommitment.jl @@ -37,6 +37,7 @@ include("model/formulations/base/system.jl") include("model/formulations/base/unit.jl") include("model/formulations/base/punit.jl") include("model/formulations/base/storage.jl") +include("model/formulations/base/interface.jl") include("model/formulations/CarArr2006/pwlcosts.jl") include("model/formulations/DamKucRajAta2016/ramp.jl") include("model/formulations/Gar1962/pwlcosts.jl") diff --git a/src/instance/read.jl b/src/instance/read.jl index 62b0230..12c9733 100644 --- a/src/instance/read.jl +++ b/src/instance/read.jl @@ -137,6 +137,7 @@ function _from_json(json; repair = true)::UnitCommitmentScenario reserves = Reserve[] profiled_units = ProfiledUnit[] storage_units = StorageUnit[] + interfaces = Interface[] function scalar(x; default = nothing) x !== nothing || return default @@ -452,6 +453,45 @@ function _from_json(json; repair = true)::UnitCommitmentScenario end end + # Read interfaces + if "Interfaces" in keys(json) + for (int_name, dict) in json["Interfaces"] + outbound_lines = TransmissionLine[] + inbound_lines = TransmissionLine[] + if "Outbound lines" in keys(dict) + outbound_lines = [ + name_to_line[l] for + l in scalar(dict["Outbound lines"], default = []) + ] + end + if "Inbound lines" in keys(dict) + inbound_lines = [ + name_to_line[l] for + l in scalar(dict["Inbound lines"], default = []) + ] + end + interface = Interface( + int_name, + length(interfaces) + 1, + outbound_lines, + inbound_lines, + timeseries( + dict["Net flow upper limit (MW)"], + default = [1e8 for t in 1:T], + ), + timeseries( + dict["Net flow lower limit (MW)"], + default = [-1e8 for t in 1:T], + ), + timeseries( + dict["Flow limit penalty (\$/MW)"], + default = [5000.0 for t in 1:T], + ), + ) + push!(interfaces, interface) + end + end + scenario = UnitCommitmentScenario( name = scenario_name, probability = probability, @@ -474,8 +514,11 @@ function _from_json(json; repair = true)::UnitCommitmentScenario profiled_units = profiled_units, storage_units_by_name = Dict(su.name => su for su in storage_units), storage_units = storage_units, + interfaces_by_name = Dict(i.name => i for i in interfaces), + interfaces = interfaces, isf = spzeros(Float64, length(lines), length(buses) - 1), lodf = spzeros(Float64, length(lines), length(lines)), + interface_isf = spzeros(Float64, length(interfaces), length(buses) - 1), ) if repair UnitCommitment.repair!(scenario) diff --git a/src/instance/structs.jl b/src/instance/structs.jl index 4082fb0..bbb0bcd 100644 --- a/src/instance/structs.jl +++ b/src/instance/structs.jl @@ -103,6 +103,16 @@ mutable struct StorageUnit max_ending_level::Float64 end +mutable struct Interface + name::String + offset::Int + outbound_lines::Vector{TransmissionLine} + inbound_lines::Vector{TransmissionLine} + net_flow_upper_limit::Vector{Float64} + net_flow_lower_limit::Vector{Float64} + flow_limit_penalty::Vector{Float64} +end + Base.@kwdef mutable struct UnitCommitmentScenario buses_by_name::Dict{AbstractString,Bus} buses::Vector{Bus} @@ -125,6 +135,9 @@ Base.@kwdef mutable struct UnitCommitmentScenario thermal_units::Vector{ThermalUnit} storage_units_by_name::Dict{AbstractString,StorageUnit} storage_units::Vector{StorageUnit} + interfaces_by_name::Dict{AbstractString,Interface} + interfaces::Vector{Interface} + interface_isf::Array{Float64,2} time::Int time_step::Int end diff --git a/src/model/build.jl b/src/model/build.jl index 0e27d42..089db92 100644 --- a/src/model/build.jl +++ b/src/model/build.jl @@ -103,6 +103,9 @@ function build_model(; _add_storage_unit!(model, su, sc) end _add_system_wide_eqs!(model, sc) + for ifc in sc.interfaces + _add_interface!(model, ifc, formulation.transmission, sc) + end end @objective(model, Min, model[:obj]) end diff --git a/src/model/formulations/base/interface.jl b/src/model/formulations/base/interface.jl new file mode 100644 index 0000000..00c2b8e --- /dev/null +++ b/src/model/formulations/base/interface.jl @@ -0,0 +1,46 @@ +# 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. + +function _add_interface!( + model::JuMP.Model, + ifc::Interface, + f::ShiftFactorsFormulation, + sc::UnitCommitmentScenario, +)::Nothing + overflow = _init(model, :interface_overflow) + net_injection = _init(model, :net_injection) + for t in 1:model[:instance].time + # define the net flow variable + flow = @variable(model, base_name = "interface_flow[$(ifc.name),$t]") + # define the overflow variable + overflow[sc.name, ifc.name, t] = @variable(model, lower_bound = 0) + # constraints: lb - v <= flow <= ub + v + @constraint( + model, + flow <= + ifc.net_flow_upper_limit[t] + overflow[sc.name, ifc.name, t] + ) + @constraint( + model, + -flow <= + -ifc.net_flow_lower_limit[t] + overflow[sc.name, ifc.name, t] + ) + # constraint: flow value is calculated from the interface ISF matrix + @constraint( + model, + flow == sum( + net_injection[sc.name, b.name, t] * + sc.interface_isf[ifc.offset, b.offset] for + b in sc.buses if b.offset > 0 + ) + ) + # make overflow part of the objective as a punishment term + add_to_expression!( + model[:obj], + overflow[sc.name, ifc.name, t], + ifc.flow_limit_penalty[t] * sc.probability, + ) + end + return +end diff --git a/src/model/formulations/base/line.jl b/src/model/formulations/base/line.jl index 98d5f2c..52eb199 100644 --- a/src/model/formulations/base/line.jl +++ b/src/model/formulations/base/line.jl @@ -26,9 +26,11 @@ function _setup_transmission( )::Nothing isf = formulation.precomputed_isf lodf = formulation.precomputed_lodf + interface_isf = nothing if length(sc.buses) == 1 isf = zeros(0, 0) lodf = zeros(0, 0) + interface_isf = zeros(0, 0) elseif isf === nothing @info "Computing injection shift factors..." time_isf = @elapsed begin @@ -36,6 +38,11 @@ function _setup_transmission( buses = sc.buses, lines = sc.lines, ) + interface_isf = + UnitCommitment._interface_injection_shift_factors( + interfaces = sc.interfaces, + isf = isf, + ) end @info @sprintf("Computed ISF in %.2f seconds", time_isf) @info "Computing line outage factors..." @@ -53,9 +60,13 @@ function _setup_transmission( formulation.lodf_cutoff ) isf[abs.(isf). 0) + optimizer = optimizer_with_attributes( + HiGHS.Optimizer, + "log_to_console" => false, + ) model = UnitCommitment.build_model( instance = modified, optimizer = optimizer, @@ -58,7 +61,10 @@ function transform_slice_test() end # Should be able to build model without errors - optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) + optimizer = optimizer_with_attributes( + HiGHS.Optimizer, + "log_to_console" => false, + ) model = UnitCommitment.build_model( instance = modified, optimizer = optimizer, @@ -88,7 +94,34 @@ function transform_slice_test() end # Should be able to build model without errors - optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0) + optimizer = optimizer_with_attributes( + HiGHS.Optimizer, + "log_to_console" => false, + ) + model = UnitCommitment.build_model( + instance = modified, + optimizer = optimizer, + variable_names = true, + ) + end + + @testset "slice interfaces" begin + instance = UnitCommitment.read(fixture("case14-interface.json.gz")) + modified = UnitCommitment.slice(instance, 1:3) + sc = modified.scenarios[1] + + # Should update all time-dependent fields + for ifc in sc.interfaces + @test length(ifc.net_flow_upper_limit) == 3 + @test length(ifc.net_flow_lower_limit) == 3 + @test length(ifc.flow_limit_penalty) == 3 + end + + # Should be able to build model without errors + optimizer = optimizer_with_attributes( + HiGHS.Optimizer, + "log_to_console" => false, + ) model = UnitCommitment.build_model( instance = modified, optimizer = optimizer,