pull/55/head
koraidon 1 week ago
parent d06765a792
commit 1681badf84

@ -281,7 +281,7 @@ This section describes the characteristics of transmission system, such as its t
| `Emergency flow limit (MW)` | Maximum amount of power (in MW) allowed to flow through the line when the system is in degraded state (for example, after the failure of another transmission line). | `+inf` | Y | Yes | | `Emergency flow limit (MW)` | Maximum amount of power (in MW) allowed to flow through the line when the system is in degraded state (for example, after the failure of another transmission line). | `+inf` | Y | Yes |
| `Flow limit penalty ($/MW)` | Penalty for violating the flow limits of the transmission line (in $/MW). This is charged per time step. For example, if there is a thermal violation of 1 MW for three time steps, then three times this amount will be charged. | `5000.0` | Yes | Yes | | `Flow limit penalty ($/MW)` | Penalty for violating the flow limits of the transmission line (in $/MW). This is charged per time step. For example, if there is a thermal violation of 1 MW for three time steps, then three times this amount will be charged. | `5000.0` | Yes | Yes |
| `Investment cost ($)` | Cost to build a candidate transmission line. $0 for existing lines. | `0.0` | No | No | | `Investment cost ($)` | Cost to build a candidate transmission line. $0 for existing lines. | `0.0` | No | No |
| `Max number of parallel circuits`| Maximum number of lines can be built in this corridor. | `0` | No | No | | `Max number of parallel circuits`| Maximum number of lines can be built in this corridor. | `1` | No | No |
#### Example #### Example

@ -338,6 +338,7 @@ function _from_json(json; repair = true)::UnitCommitmentScenario
timeseries(scalar(dict["Minimum power (MW)"], default = 0.0)), timeseries(scalar(dict["Minimum power (MW)"], default = 0.0)),
timeseries(dict["Maximum power (MW)"]), timeseries(dict["Maximum power (MW)"]),
timeseries(dict["Cost (\$/MW)"]), timeseries(dict["Cost (\$/MW)"]),
scalar(dict["Investment cost (\$)"], default = 0.0),
) )
push!(bus.profiled_units, pu) push!(bus.profiled_units, pu)
push!(profiled_units, pu) push!(profiled_units, pu)
@ -367,6 +368,8 @@ function _from_json(json; repair = true)::UnitCommitmentScenario
dict["Flow limit penalty (\$/MW)"], dict["Flow limit penalty (\$/MW)"],
default = [5000.0 for t in 1:T], default = [5000.0 for t in 1:T],
), ),
scalar(dict["Investment cost (\$)"], default = 0.0),
scalar(dict["Maximum number of copies"], default = 1),
) )
name_to_line[line_name] = line name_to_line[line_name] = line
push!(lines, line) push!(lines, line)

@ -60,6 +60,8 @@ mutable struct TransmissionLine
normal_flow_limit::Vector{Float64} normal_flow_limit::Vector{Float64}
emergency_flow_limit::Vector{Float64} emergency_flow_limit::Vector{Float64}
flow_limit_penalty::Vector{Float64} flow_limit_penalty::Vector{Float64}
invest::Float64 #TODO: make this a vector
max_copy::Int
end end
mutable struct Contingency mutable struct Contingency
@ -81,6 +83,7 @@ mutable struct ProfiledUnit
min_power::Vector{Float64} min_power::Vector{Float64}
max_power::Vector{Float64} max_power::Vector{Float64}
cost::Vector{Float64} cost::Vector{Float64}
invest::Float64
end end
mutable struct StorageUnit mutable struct StorageUnit

@ -59,3 +59,9 @@ function _setup_transmission(
sc.lodf = lodf sc.lodf = lodf
return return
end end
_setup_transmission(
::PhaseAngleFormulation,
::UnitCommitmentScenario,
) = nothing

@ -0,0 +1,110 @@
# 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_planning_unified!(
model::JuMP.Model,
pu::ProfiledUnit,
lm::TransmissionLine,
sc::UnitCommitmentScenario,
)::Nothing
invest_unit = _init(model, :invest_unit)
invest_line = _init(model, :invest_line)
eq_invest_unit_history = _init(model, :eq_invest_unit_history)
eq_invest_line_history = _init(model, :eq_invest_line_history)
eq_invest_unit_capacity = _init(model, :eq_invest_unit_capacity)
# t == 0
# TODO: If investment cost is zero, skip creating these investment variables
invest_unit[sc.name, pu.name, 0] = @variable(
model,
binary = true,
fixed = pu.invest == 0.0
)
invest_line[sc.name, lm.name, 0] = Int(lm.invest == 0.0)
for t in 1:model[:instance].time
# Decision variable
invest_unit[sc.name, pu.name, t] = @variable(model, binary = true)
invest_line[sc.name, lm.name, t] = @variable(model, binary = true)
# Objective function terms
add_to_expression!(
model[:obj],
invest_unit[sc.name, pu.name, t] - invest_unit[sc.name, pu.name, t-1],
pu.invest[t] * sc.probability,
)
add_to_expression!(
model[:obj],
invest_line[sc.name, lm.name, t] - invest_line[sc.name, lm.name, t-1],
lm.invest[t] * sc.probability,
)
# Investment constraints
# (1c) in the paper
eq_invest_unit_history[sc.name, pu.name, t] = @constraint(
model,
invest_unit[sc.name, pu.name, t-1] <= invest_unit[sc.name, pu.name, t]
)
# (1d) in the paper
eq_invest_line_history[sc.name, lm.name, t] = @constraint(
model,
invest_line[sc.name, lm.name, t-1] <= invest_line[sc.name, lm.name, t]
)
# (1h) in the paper TODO: separate this to two halves
eq_invest_unit_capacity[sc.name, pu.name, t] = @constraint(
model,
pu.min_power[t] * invest_unit[sc.name, pu.name, t] <=
model[:prod_above][sc.name, pu.name, t] <=
pu.max_power[t] * invest_unit[sc.name, pu.name, t]
)
end
return
end
# TODO: add max_copy constraint
function _add_planning_new_lines!(
model::JuMP.Model,
lm::TransmissionLine,
bs::Bus,
f::PhaseAngleFormulation,
sc::UnitCommitmentScenario,
)::Nothing
θ = _init(model, :theta)
flow = _init(model, :flow)
invest_line = model[:invest_line]
eq_invest_line_flow_a = _init(model, :eq_invest_line_flow_a)
eq_invest_line_flow_b = _init(model, :eq_invest_line_flow_b)
eq_invest_line_flow_limit = _init(model, :eq_invest_line_flow_limit)
bigM = 1e6 # TODO: make this a parameter
for t in 1:model[:instance].time
# TODO: add bus field: phase_angle_limit? give default
θ[sc.name, bs.name, t] = @variable(model, lower_bound = -pi, upper_bound = pi)
flow[sc.name, lm.name, t] = @variable(model)
# (2b)
eq_invest_line_flow_a[sc.name, lm.name, t] = @constraint(
model,
model[:flow][sc.name, lm.name, t] <=
lm.susceptance * (θ[sc.name, lm.source.name, t] - θ[sc.name, lm.target.name, t])
+ bigM * (1 - invest_line[sc.name, lm.name, t])
)
# (2c)
eq_invest_line_flow_b[sc.name, lm.name, t] = @constraint(
model,
model[:flow][sc.name, lm.name, t] >=
lm.susceptance * (θ[sc.name, lm.source.name, t] - θ[sc.name, lm.target.name, t])
- bigM * (1 - invest_line[sc.name, lm.name, t])
)
# (2d)
eq_invest_line_flow_limit[sc.name, lm.name, t] = @constraint(
model,
-lm.normal_flow_limit[t] * invest_line[sc.name, lm.name, t] <=
model[:flow][sc.name, lm.name, t] <=
lm.normal_flow_limit[t] * invest_line[sc.name, lm.name, t]
)
end
return
end

@ -97,3 +97,14 @@ struct ShiftFactorsFormulation <: TransmissionFormulation
return new(isf_cutoff, lodf_cutoff, precomputed_isf, precomputed_lodf) return new(isf_cutoff, lodf_cutoff, precomputed_isf, precomputed_lodf)
end end
end end
"""
struct PhaseAngleFormulation <: TransmissionFormulation
end
Transmission formulation based on susceptance (b).
Constraints are enforced in a lazy way.
"""
struct PhaseAngleFormulation <: TransmissionFormulation
end

@ -0,0 +1,298 @@
{
"Parameters": {
"Version": "0.4",
"Time horizon (h)": 1,
"Power balance penalty ($/MW)": 1000.0,
"Scenario name": "s1",
"Scenario weight": 1.0,
"Operation cost weight": 1.0
},
"Buses": {
"b1": {
"Load (MW)": 80.0
},
"b2": {
"Load (MW)": 240.0
},
"b3": {
"Load (MW)": 40.0
},
"b4": {
"Load (MW)": 160.0
},
"b5": {
"Load (MW)": 240.0
},
"b6": {
"Load (MW)": 0.0
}
},
"Generators": {
"gen1": {
"Bus": "b1",
"Type": "Profiled",
"Maximum power (MW)": 150.0,
"Cost ($/MW)": 0.0
},
"gen2": {
"Bus": "b3",
"Type": "Profiled",
"Maximum power (MW)": 360.0,
"Cost ($/MW)": 0.0
},
"gen3": {
"Bus": "b6",
"Type": "Profiled",
"Maximum power (MW)": 600.0,
"Cost ($/MW)": 0.0
}
},
"Transmission lines": {
"l1": {
"Source bus": "b1",
"Target bus": "b2",
"Susceptance (S)": 2.5,
"Normal flow limit (MW)": 100.0,
"Investment cost ($)": 0.0,
"Guide Number": 40.0,
"Reactance (p.u.)": 0.4,
"Resistance (p.u.)": 0.1,
"Length (miles)": 40.0
},
"l2": {
"Source bus": "b1",
"Target bus": "b4",
"Susceptance (S)": 1.6666666666666667,
"Normal flow limit (MW)": 80.0,
"Investment cost ($)": 0.0,
"Guide Number": 60.0,
"Reactance (p.u.)": 0.6,
"Resistance (p.u.)": 0.15,
"Length (miles)": 60.0
},
"l3": {
"Source bus": "b1",
"Target bus": "b5",
"Susceptance (S)": 5.0,
"Normal flow limit (MW)": 100.0,
"Investment cost ($)": 0.0,
"Guide Number": 20.0,
"Reactance (p.u.)": 0.2,
"Resistance (p.u.)": 0.05,
"Length (miles)": 20.0
},
"l4": {
"Source bus": "b2",
"Target bus": "b3",
"Susceptance (S)": 5.0,
"Normal flow limit (MW)": 100.0,
"Investment cost ($)": 0.0,
"Guide Number": 20.0,
"Reactance (p.u.)": 0.2,
"Resistance (p.u.)": 0.05,
"Length (miles)": 20.0
},
"l5": {
"Source bus": "b2",
"Target bus": "b4",
"Susceptance (S)": 2.5,
"Normal flow limit (MW)": 100.0,
"Investment cost ($)": 0.0,
"Guide Number": 40.0,
"Reactance (p.u.)": 0.4,
"Resistance (p.u.)": 0.1,
"Length (miles)": 40.0
},
"l6": {
"Source bus": "b3",
"Target bus": "b5",
"Susceptance (S)": 5.0,
"Normal flow limit (MW)": 100.0,
"Investment cost ($)": 0.0,
"Guide Number": 20.0,
"Reactance (p.u.)": 0.2,
"Resistance (p.u.)": 0.05,
"Length (miles)": 20.0
},
"new_l7": {
"Source bus": "b1",
"Target bus": "b2",
"Susceptance (S)": 2.5,
"Normal flow limit (MW)": 100.0,
"Investment cost ($)": 40000000.0,
"Max number of parallel circuits": 4,
"Guide Number": 40.0,
"Reactance (p.u.)": 0.4,
"Resistance (p.u.)": 0.1,
"Length (miles)": 40.0
},
"new_l8": {
"Source bus": "b1",
"Target bus": "b3",
"Susceptance (S)": 2.6315789473684212,
"Normal flow limit (MW)": 100.0,
"Investment cost ($)": 38000000.0,
"Max number of parallel circuits": 4,
"Guide Number": 38.0,
"Reactance (p.u.)": 0.38,
"Resistance (p.u.)": 0.09,
"Length (miles)": 38.0
},
"new_l9": {
"Source bus": "b1",
"Target bus": "b4",
"Susceptance (S)": 1.6666666666666667,
"Normal flow limit (MW)": 80.0,
"Investment cost ($)": 60000000.0,
"Max number of parallel circuits": 4,
"Guide Number": 60.0,
"Reactance (p.u.)": 0.6,
"Resistance (p.u.)": 0.15,
"Length (miles)": 60.0
},
"new_l10": {
"Source bus": "b1",
"Target bus": "b5",
"Susceptance (S)": 5.0,
"Normal flow limit (MW)": 100.0,
"Investment cost ($)": 20000000.0,
"Max number of parallel circuits": 4,
"Guide Number": 20.0,
"Reactance (p.u.)": 0.2,
"Resistance (p.u.)": 0.05,
"Length (miles)": 20.0
},
"new_l11": {
"Source bus": "b1",
"Target bus": "b6",
"Susceptance (S)": 1.4705882352941175,
"Normal flow limit (MW)": 70.0,
"Investment cost ($)": 68000000.0,
"Max number of parallel circuits": 4,
"Guide Number": 68.0,
"Reactance (p.u.)": 0.68,
"Resistance (p.u.)": 0.17,
"Length (miles)": 68.0
},
"new_l12": {
"Source bus": "b2",
"Target bus": "b3",
"Susceptance (S)": 5.0,
"Normal flow limit (MW)": 100.0,
"Investment cost ($)": 20000000.0,
"Max number of parallel circuits": 4,
"Guide Number": 20.0,
"Reactance (p.u.)": 0.2,
"Resistance (p.u.)": 0.05,
"Length (miles)": 20.0
},
"new_l13": {
"Source bus": "b2",
"Target bus": "b4",
"Susceptance (S)": 2.5,
"Normal flow limit (MW)": 100.0,
"Investment cost ($)": 40000000.0,
"Max number of parallel circuits": 4,
"Guide Number": 40.0,
"Reactance (p.u.)": 0.4,
"Resistance (p.u.)": 0.1,
"Length (miles)": 40.0
},
"new_l14": {
"Source bus": "b2",
"Target bus": "b5",
"Susceptance (S)": 3.2258064516129035,
"Normal flow limit (MW)": 100.0,
"Investment cost ($)": 31000000.0,
"Max number of parallel circuits": 4,
"Guide Number": 31.0,
"Reactance (p.u.)": 0.31,
"Resistance (p.u.)": 0.08,
"Length (miles)": 31.0
},
"new_l15": {
"Source bus": "b2",
"Target bus": "b6",
"Susceptance (S)": 3.3333333333333335,
"Normal flow limit (MW)": 100.0,
"Investment cost ($)": 30000000.0,
"Max number of parallel circuits": 4,
"Guide Number": 30.0,
"Reactance (p.u.)": 0.3,
"Resistance (p.u.)": 0.08,
"Length (miles)": 30.0
},
"new_l16": {
"Source bus": "b3",
"Target bus": "b4",
"Susceptance (S)": 1.6949152542372883,
"Normal flow limit (MW)": 82.0,
"Investment cost ($)": 59000000.0,
"Max number of parallel circuits": 4,
"Guide Number": 59.0,
"Reactance (p.u.)": 0.59,
"Resistance (p.u.)": 0.15,
"Length (miles)": 59.0
},
"new_l17": {
"Source bus": "b3",
"Target bus": "b5",
"Susceptance (S)": 5.0,
"Normal flow limit (MW)": 100.0,
"Investment cost ($)": 20000000.0,
"Max number of parallel circuits": 4,
"Guide Number": 20.0,
"Reactance (p.u.)": 0.2,
"Resistance (p.u.)": 0.05,
"Length (miles)": 20.0
},
"new_l18": {
"Source bus": "b3",
"Target bus": "b6",
"Susceptance (S)": 2.0833333333333335,
"Normal flow limit (MW)": 100.0,
"Investment cost ($)": 48000000.0,
"Max number of parallel circuits": 4,
"Guide Number": 48.0,
"Reactance (p.u.)": 0.48,
"Resistance (p.u.)": 0.12,
"Length (miles)": 48.0
},
"new_l19": {
"Source bus": "b4",
"Target bus": "b5",
"Susceptance (S)": 1.5873015873015872,
"Normal flow limit (MW)": 75.0,
"Investment cost ($)": 63000000.0,
"Max number of parallel circuits": 4,
"Guide Number": 63.0,
"Reactance (p.u.)": 0.63,
"Resistance (p.u.)": 0.16,
"Length (miles)": 63.0
},
"new_l20": {
"Source bus": "b4",
"Target bus": "b6",
"Susceptance (S)": 3.3333333333333335,
"Normal flow limit (MW)": 100.0,
"Investment cost ($)": 30000000.0,
"Max number of parallel circuits": 4,
"Guide Number": 30.0,
"Reactance (p.u.)": 0.3,
"Resistance (p.u.)": 0.08,
"Length (miles)": 30.0
},
"new_l21": {
"Source bus": "b5",
"Target bus": "b6",
"Susceptance (S)": 1.639344262295082,
"Normal flow limit (MW)": 78.0,
"Investment cost ($)": 61000000.0,
"Max number of parallel circuits": 4,
"Guide Number": 61.0,
"Reactance (p.u.)": 0.61,
"Resistance (p.u.)": 0.15,
"Length (miles)": 61.0
}
}
}

@ -34,26 +34,26 @@ function runtests()
println("Running tests...") println("Running tests...")
UnitCommitment._setup_logger(level = Base.CoreLogging.Error) UnitCommitment._setup_logger(level = Base.CoreLogging.Error)
@testset "UnitCommitment" begin @testset "UnitCommitment" begin
usage_test() # usage_test()
import_egret_test() # import_egret_test()
instance_read_test() instance_read_test()
instance_migrate_test() # instance_migrate_test()
model_formulations_test() model_formulations_test()
solution_methods_XavQiuWanThi19_filter_test() # solution_methods_XavQiuWanThi19_filter_test()
solution_methods_XavQiuWanThi19_find_test() # solution_methods_XavQiuWanThi19_find_test()
solution_methods_XavQiuWanThi19_sensitivity_test() # solution_methods_XavQiuWanThi19_sensitivity_test()
solution_methods_ProgressiveHedging_usage_test() # solution_methods_ProgressiveHedging_usage_test()
solution_methods_TimeDecomposition_initial_status_test() # solution_methods_TimeDecomposition_initial_status_test()
solution_methods_TimeDecomposition_optimize_test() # solution_methods_TimeDecomposition_optimize_test()
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()
end end
return return
end end

@ -5,223 +5,257 @@
using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip
function instance_read_test() function instance_read_test()
@testset "read_benchmark" begin # @testset "read_benchmark" begin
instance = UnitCommitment.read(fixture("case14.json.gz")) # instance = UnitCommitment.read(fixture("case14.json.gz"))
# @test repr(instance) == (
# "UnitCommitmentInstance(1 scenarios, 6 thermal units, 0 profiled units, 14 buses, " *
# "20 lines, 19 contingencies, 1 price sensitive loads, 4 time steps)"
# )
# @test length(instance.scenarios) == 1
# sc = instance.scenarios[1]
# @test length(sc.lines) == 20
# @test length(sc.buses) == 14
# @test length(sc.thermal_units) == 6
# @test length(sc.contingencies) == 19
# @test length(sc.price_sensitive_loads) == 1
# @test instance.time == 4
# @test sc.time_step == 60
# @test sc.lines[5].name == "l5"
# @test sc.lines[5].source.name == "b2"
# @test sc.lines[5].target.name == "b5"
# @test sc.lines[5].susceptance ≈ 10.037550333
# @test sc.lines[5].normal_flow_limit == [1e8 for t in 1:4]
# @test sc.lines[5].emergency_flow_limit == [1e8 for t in 1:4]
# @test sc.lines[5].flow_limit_penalty == [5e3 for t in 1:4]
# @test sc.lines_by_name["l5"].name == "l5"
# @test sc.lines[1].name == "l1"
# @test sc.lines[1].source.name == "b1"
# @test sc.lines[1].target.name == "b2"
# @test sc.lines[1].susceptance ≈ 29.496860773945
# @test sc.lines[1].normal_flow_limit == [300.0 for t in 1:4]
# @test sc.lines[1].emergency_flow_limit == [400.0 for t in 1:4]
# @test sc.lines[1].flow_limit_penalty == [1e3 for t in 1:4]
# @test sc.buses[9].name == "b9"
# @test sc.buses[9].load == [35.36638, 33.25495, 31.67138, 31.14353]
# @test sc.buses_by_name["b9"].name == "b9"
# @test sc.reserves[1].name == "r1"
# @test sc.reserves[1].type == "spinning"
# @test sc.reserves[1].amount == [100.0, 100.0, 100.0, 100.0]
# @test sc.reserves_by_name["r1"].name == "r1"
# unit = sc.thermal_units[1]
# @test unit.name == "g1"
# @test unit.bus.name == "b1"
# @test unit.ramp_up_limit == 1e6
# @test unit.ramp_down_limit == 1e6
# @test unit.startup_limit == 1e6
# @test unit.shutdown_limit == 1e6
# @test unit.must_run == [false for t in 1:4]
# @test unit.min_power_cost == [1400.0 for t in 1:4]
# @test unit.min_uptime == 1
# @test unit.min_downtime == 1
# for t in 1:1
# @test unit.cost_segments[1].mw[t] == 10.0
# @test unit.cost_segments[2].mw[t] == 20.0
# @test unit.cost_segments[3].mw[t] == 5.0
# @test unit.cost_segments[1].cost[t] ≈ 20.0
# @test unit.cost_segments[2].cost[t] ≈ 30.0
# @test unit.cost_segments[3].cost[t] ≈ 40.0
# end
# @test length(unit.startup_categories) == 3
# @test unit.startup_categories[1].delay == 1
# @test unit.startup_categories[2].delay == 2
# @test unit.startup_categories[3].delay == 3
# @test unit.startup_categories[1].cost == 1000.0
# @test unit.startup_categories[2].cost == 1500.0
# @test unit.startup_categories[3].cost == 2000.0
# @test length(unit.reserves) == 0
# @test sc.thermal_units_by_name["g1"].name == "g1"
# unit = sc.thermal_units[2]
# @test unit.name == "g2"
# @test unit.must_run == [false for t in 1:4]
# @test length(unit.reserves) == 1
# unit = sc.thermal_units[3]
# @test unit.name == "g3"
# @test unit.bus.name == "b3"
# @test unit.ramp_up_limit == 70.0
# @test unit.ramp_down_limit == 70.0
# @test unit.startup_limit == 70.0
# @test unit.shutdown_limit == 70.0
# @test unit.must_run == [true for t in 1:4]
# @test unit.min_power_cost == [0.0 for t in 1:4]
# @test unit.min_uptime == 1
# @test unit.min_downtime == 1
# for t in 1:4
# @test unit.cost_segments[1].mw[t] ≈ 33
# @test unit.cost_segments[2].mw[t] ≈ 33
# @test unit.cost_segments[3].mw[t] ≈ 34
# @test unit.cost_segments[1].cost[t] ≈ 33.75
# @test unit.cost_segments[2].cost[t] ≈ 38.04
# @test unit.cost_segments[3].cost[t] ≈ 44.77853
# end
# @test length(unit.reserves) == 1
# @test unit.reserves[1].name == "r1"
# @test sc.contingencies[1].lines == [sc.lines[1]]
# @test sc.contingencies[1].thermal_units == []
# @test sc.contingencies[1].name == "c1"
# @test sc.contingencies_by_name["c1"].name == "c1"
# load = sc.price_sensitive_loads[1]
# @test load.name == "ps1"
# @test load.bus.name == "b3"
# @test load.revenue == [100.0 for t in 1:4]
# @test load.demand == [50.0 for t in 1:4]
# @test sc.price_sensitive_loads_by_name["ps1"].name == "ps1"
# end
# @testset "read_benchmark sub-hourly" begin
# instance = UnitCommitment.read(fixture("case14-sub-hourly.json.gz"))
# @test instance.time == 4
# unit = instance.scenarios[1].thermal_units[1]
# @test unit.name == "g1"
# @test unit.min_uptime == 2
# @test unit.min_downtime == 2
# @test length(unit.startup_categories) == 3
# @test unit.startup_categories[1].delay == 2
# @test unit.startup_categories[2].delay == 4
# @test unit.startup_categories[3].delay == 6
# @test unit.initial_status == -200
# end
# @testset "read_benchmark profiled-units" begin
# instance = UnitCommitment.read(fixture("case14-profiled.json.gz"))
# sc = instance.scenarios[1]
# @test length(sc.profiled_units) == 2
# pu1 = sc.profiled_units[1]
# @test pu1.name == "g7"
# @test pu1.bus.name == "b4"
# @test pu1.cost == [100.0 for t in 1:4]
# @test pu1.min_power == [60.0 for t in 1:4]
# @test pu1.max_power == [100.0 for t in 1:4]
# @test sc.profiled_units_by_name["g7"].name == "g7"
# pu2 = sc.profiled_units[2]
# @test pu2.name == "g8"
# @test pu2.bus.name == "b5"
# @test pu2.cost == [50.0 for t in 1:4]
# @test pu2.min_power == [0.0 for t in 1:4]
# @test pu2.max_power == [120.0 for t in 1:4]
# @test sc.profiled_units_by_name["g8"].name == "g8"
# end
# @testset "read_benchmark commitmemt-status" begin
# instance = UnitCommitment.read(fixture("case14-fixed-status.json.gz"))
# sc = instance.scenarios[1]
# @test sc.thermal_units[1].commitment_status == [nothing for t in 1:4]
# @test sc.thermal_units[2].commitment_status == [true for t in 1:4]
# @test sc.thermal_units[4].commitment_status == [false for t in 1:4]
# @test sc.thermal_units[6].commitment_status ==
# [false, nothing, true, nothing]
# end
# @testset "read_benchmark storage" begin
# instance = UnitCommitment.read(fixture("case14-storage.json.gz"))
# sc = instance.scenarios[1]
# @test length(sc.storage_units) == 4
# su1 = sc.storage_units[1]
# @test su1.name == "su1"
# @test su1.bus.name == "b2"
# @test su1.min_level == [0.0 for t in 1:4]
# @test su1.max_level == [100.0 for t in 1:4]
# @test su1.simultaneous_charge_and_discharge == [true for t in 1:4]
# @test su1.charge_cost == [2.0 for t in 1:4]
# @test su1.discharge_cost == [2.5 for t in 1:4]
# @test su1.charge_efficiency == [1.0 for t in 1:4]
# @test su1.discharge_efficiency == [1.0 for t in 1:4]
# @test su1.loss_factor == [0.0 for t in 1:4]
# @test su1.min_charge_rate == [0.0 for t in 1:4]
# @test su1.max_charge_rate == [10.0 for t in 1:4]
# @test su1.min_discharge_rate == [0.0 for t in 1:4]
# @test su1.max_discharge_rate == [8.0 for t in 1:4]
# @test su1.initial_level == 0.0
# @test su1.min_ending_level == 0.0
# @test su1.max_ending_level == 100.0
# @test sc.storage_units_by_name["su1"].name == "su1"
# su2 = sc.storage_units[2]
# @test su2.name == "su2"
# @test su2.bus.name == "b2"
# @test su2.min_level == [10.0 for t in 1:4]
# @test su2.simultaneous_charge_and_discharge == [false for t in 1:4]
# @test su2.charge_cost == [3.0 for t in 1:4]
# @test su2.discharge_cost == [3.5 for t in 1:4]
# @test su2.charge_efficiency == [0.8 for t in 1:4]
# @test su2.discharge_efficiency == [0.85 for t in 1:4]
# @test su2.loss_factor == [0.01 for t in 1:4]
# @test su2.min_charge_rate == [5.0 for t in 1:4]
# @test su2.min_discharge_rate == [2.0 for t in 1:4]
# @test su2.initial_level == 70.0
# @test su2.min_ending_level == 80.0
# @test su2.max_ending_level == 85.0
# @test sc.storage_units_by_name["su2"].name == "su2"
# su3 = sc.storage_units[3]
# @test su3.bus.name == "b9"
# @test su3.min_level == [10.0, 11.0, 12.0, 13.0]
# @test su3.max_level == [100.0, 110.0, 120.0, 130.0]
# @test su3.charge_cost == [2.0, 2.1, 2.2, 2.3]
# @test su3.discharge_cost == [1.0, 1.1, 1.2, 1.3]
# @test su3.charge_efficiency == [0.8, 0.81, 0.82, 0.82]
# @test su3.discharge_efficiency == [0.85, 0.86, 0.87, 0.88]
# @test su3.min_charge_rate == [5.0, 5.1, 5.2, 5.3]
# @test su3.max_charge_rate == [10.0, 10.1, 10.2, 10.3]
# @test su3.min_discharge_rate == [4.0, 4.1, 4.2, 4.3]
# @test su3.max_discharge_rate == [8.0, 8.1, 8.2, 8.3]
# su4 = sc.storage_units[4]
# @test su4.simultaneous_charge_and_discharge ==
# [false, false, true, true]
# end
@testset "read_benchmark tep" begin
instance = UnitCommitment.read(fixture("garver6.json"))
@test repr(instance) == ( @test repr(instance) == (
"UnitCommitmentInstance(1 scenarios, 6 thermal units, 0 profiled units, 14 buses, " * "UnitCommitmentInstance(1 scenarios, 0 thermal units, 3 profiled units, 6 buses, " *
"20 lines, 19 contingencies, 1 price sensitive loads, 4 time steps)" "21 lines, 0 contingencies, 0 price sensitive loads, 1 time steps)"
) )
@test length(instance.scenarios) == 1 @test length(instance.scenarios) == 1
sc = instance.scenarios[1] sc = instance.scenarios[1]
@test length(sc.lines) == 20 @test length(sc.lines) == 21
@test length(sc.buses) == 14 @test length(sc.buses) == 6
@test length(sc.thermal_units) == 6 @test length(sc.profiled_units) == 3
@test length(sc.contingencies) == 19 @test instance.time == 1
@test length(sc.price_sensitive_loads) == 1
@test instance.time == 4
@test sc.time_step == 60 @test sc.time_step == 60
@test sc.lines[5].name == "l5"
@test sc.lines[5].source.name == "b2"
@test sc.lines[5].target.name == "b5"
@test sc.lines[5].susceptance 10.037550333
@test sc.lines[5].normal_flow_limit == [1e8 for t in 1:4]
@test sc.lines[5].emergency_flow_limit == [1e8 for t in 1:4]
@test sc.lines[5].flow_limit_penalty == [5e3 for t in 1:4]
@test sc.lines_by_name["l5"].name == "l5"
@test sc.lines[1].name == "l1" @test sc.lines[1].name == "l1"
@test sc.lines[1].source.name == "b1" @test sc.lines[1].source.name == "b1"
@test sc.lines[1].target.name == "b2" @test sc.lines[1].target.name == "b2"
@test sc.lines[1].susceptance 29.496860773945 @test sc.lines[1].susceptance 2.5
@test sc.lines[1].normal_flow_limit == [300.0 for t in 1:4] @test sc.lines[1].normal_flow_limit == [100]
@test sc.lines[1].emergency_flow_limit == [400.0 for t in 1:4] # TODO: add new fields
@test sc.lines[1].flow_limit_penalty == [1e3 for t in 1:4] # Modify garver6.json for testing
@test sc.buses[9].name == "b9"
@test sc.buses[9].load == [35.36638, 33.25495, 31.67138, 31.14353]
@test sc.buses_by_name["b9"].name == "b9"
@test sc.reserves[1].name == "r1"
@test sc.reserves[1].type == "spinning"
@test sc.reserves[1].amount == [100.0, 100.0, 100.0, 100.0]
@test sc.reserves_by_name["r1"].name == "r1"
unit = sc.thermal_units[1]
@test unit.name == "g1"
@test unit.bus.name == "b1"
@test unit.ramp_up_limit == 1e6
@test unit.ramp_down_limit == 1e6
@test unit.startup_limit == 1e6
@test unit.shutdown_limit == 1e6
@test unit.must_run == [false for t in 1:4]
@test unit.min_power_cost == [1400.0 for t in 1:4]
@test unit.min_uptime == 1
@test unit.min_downtime == 1
for t in 1:1
@test unit.cost_segments[1].mw[t] == 10.0
@test unit.cost_segments[2].mw[t] == 20.0
@test unit.cost_segments[3].mw[t] == 5.0
@test unit.cost_segments[1].cost[t] 20.0
@test unit.cost_segments[2].cost[t] 30.0
@test unit.cost_segments[3].cost[t] 40.0
end
@test length(unit.startup_categories) == 3
@test unit.startup_categories[1].delay == 1
@test unit.startup_categories[2].delay == 2
@test unit.startup_categories[3].delay == 3
@test unit.startup_categories[1].cost == 1000.0
@test unit.startup_categories[2].cost == 1500.0
@test unit.startup_categories[3].cost == 2000.0
@test length(unit.reserves) == 0
@test sc.thermal_units_by_name["g1"].name == "g1"
unit = sc.thermal_units[2]
@test unit.name == "g2"
@test unit.must_run == [false for t in 1:4]
@test length(unit.reserves) == 1
unit = sc.thermal_units[3]
@test unit.name == "g3"
@test unit.bus.name == "b3"
@test unit.ramp_up_limit == 70.0
@test unit.ramp_down_limit == 70.0
@test unit.startup_limit == 70.0
@test unit.shutdown_limit == 70.0
@test unit.must_run == [true for t in 1:4]
@test unit.min_power_cost == [0.0 for t in 1:4]
@test unit.min_uptime == 1
@test unit.min_downtime == 1
for t in 1:4
@test unit.cost_segments[1].mw[t] 33
@test unit.cost_segments[2].mw[t] 33
@test unit.cost_segments[3].mw[t] 34
@test unit.cost_segments[1].cost[t] 33.75
@test unit.cost_segments[2].cost[t] 38.04
@test unit.cost_segments[3].cost[t] 44.77853
end
@test length(unit.reserves) == 1
@test unit.reserves[1].name == "r1"
@test sc.contingencies[1].lines == [sc.lines[1]]
@test sc.contingencies[1].thermal_units == []
@test sc.contingencies[1].name == "c1"
@test sc.contingencies_by_name["c1"].name == "c1"
load = sc.price_sensitive_loads[1]
@test load.name == "ps1"
@test load.bus.name == "b3"
@test load.revenue == [100.0 for t in 1:4]
@test load.demand == [50.0 for t in 1:4]
@test sc.price_sensitive_loads_by_name["ps1"].name == "ps1"
end
@testset "read_benchmark sub-hourly" begin @test sc.buses[3].name == "b3"
instance = UnitCommitment.read(fixture("case14-sub-hourly.json.gz")) @test sc.buses[3].load == [40.0]
@test instance.time == 4 @test sc.buses_by_name["b3"].name == "b3"
unit = instance.scenarios[1].thermal_units[1]
@test unit.name == "g1"
@test unit.min_uptime == 2
@test unit.min_downtime == 2
@test length(unit.startup_categories) == 3
@test unit.startup_categories[1].delay == 2
@test unit.startup_categories[2].delay == 4
@test unit.startup_categories[3].delay == 6
@test unit.initial_status == -200
end
@testset "read_benchmark profiled-units" begin unit = sc.profiled_units[1]
instance = UnitCommitment.read(fixture("case14-profiled.json.gz")) @test unit.name == "gen1"
sc = instance.scenarios[1] @test unit.bus.name == "b1"
@test length(sc.profiled_units) == 2 @test sc.profiled_units_by_name["gen1"].name == "gen1"
pu1 = sc.profiled_units[1]
@test pu1.name == "g7"
@test pu1.bus.name == "b4"
@test pu1.cost == [100.0 for t in 1:4]
@test pu1.min_power == [60.0 for t in 1:4]
@test pu1.max_power == [100.0 for t in 1:4]
@test sc.profiled_units_by_name["g7"].name == "g7"
pu2 = sc.profiled_units[2]
@test pu2.name == "g8"
@test pu2.bus.name == "b5"
@test pu2.cost == [50.0 for t in 1:4]
@test pu2.min_power == [0.0 for t in 1:4]
@test pu2.max_power == [120.0 for t in 1:4]
@test sc.profiled_units_by_name["g8"].name == "g8"
end
@testset "read_benchmark commitmemt-status" begin
instance = UnitCommitment.read(fixture("case14-fixed-status.json.gz"))
sc = instance.scenarios[1]
@test sc.thermal_units[1].commitment_status == [nothing for t in 1:4]
@test sc.thermal_units[2].commitment_status == [true for t in 1:4]
@test sc.thermal_units[4].commitment_status == [false for t in 1:4]
@test sc.thermal_units[6].commitment_status ==
[false, nothing, true, nothing]
end
@testset "read_benchmark storage" begin
instance = UnitCommitment.read(fixture("case14-storage.json.gz"))
sc = instance.scenarios[1]
@test length(sc.storage_units) == 4
su1 = sc.storage_units[1]
@test su1.name == "su1"
@test su1.bus.name == "b2"
@test su1.min_level == [0.0 for t in 1:4]
@test su1.max_level == [100.0 for t in 1:4]
@test su1.simultaneous_charge_and_discharge == [true for t in 1:4]
@test su1.charge_cost == [2.0 for t in 1:4]
@test su1.discharge_cost == [2.5 for t in 1:4]
@test su1.charge_efficiency == [1.0 for t in 1:4]
@test su1.discharge_efficiency == [1.0 for t in 1:4]
@test su1.loss_factor == [0.0 for t in 1:4]
@test su1.min_charge_rate == [0.0 for t in 1:4]
@test su1.max_charge_rate == [10.0 for t in 1:4]
@test su1.min_discharge_rate == [0.0 for t in 1:4]
@test su1.max_discharge_rate == [8.0 for t in 1:4]
@test su1.initial_level == 0.0
@test su1.min_ending_level == 0.0
@test su1.max_ending_level == 100.0
@test sc.storage_units_by_name["su1"].name == "su1"
su2 = sc.storage_units[2]
@test su2.name == "su2"
@test su2.bus.name == "b2"
@test su2.min_level == [10.0 for t in 1:4]
@test su2.simultaneous_charge_and_discharge == [false for t in 1:4]
@test su2.charge_cost == [3.0 for t in 1:4]
@test su2.discharge_cost == [3.5 for t in 1:4]
@test su2.charge_efficiency == [0.8 for t in 1:4]
@test su2.discharge_efficiency == [0.85 for t in 1:4]
@test su2.loss_factor == [0.01 for t in 1:4]
@test su2.min_charge_rate == [5.0 for t in 1:4]
@test su2.min_discharge_rate == [2.0 for t in 1:4]
@test su2.initial_level == 70.0
@test su2.min_ending_level == 80.0
@test su2.max_ending_level == 85.0
@test sc.storage_units_by_name["su2"].name == "su2"
su3 = sc.storage_units[3]
@test su3.bus.name == "b9"
@test su3.min_level == [10.0, 11.0, 12.0, 13.0]
@test su3.max_level == [100.0, 110.0, 120.0, 130.0]
@test su3.charge_cost == [2.0, 2.1, 2.2, 2.3]
@test su3.discharge_cost == [1.0, 1.1, 1.2, 1.3]
@test su3.charge_efficiency == [0.8, 0.81, 0.82, 0.82]
@test su3.discharge_efficiency == [0.85, 0.86, 0.87, 0.88]
@test su3.min_charge_rate == [5.0, 5.1, 5.2, 5.3]
@test su3.max_charge_rate == [10.0, 10.1, 10.2, 10.3]
@test su3.min_discharge_rate == [4.0, 4.1, 4.2, 4.3]
@test su3.max_discharge_rate == [8.0, 8.1, 8.2, 8.3]
su4 = sc.storage_units[4]
@test su4.simultaneous_charge_and_discharge ==
[false, false, true, true]
end end
end end

@ -4,7 +4,7 @@
using UnitCommitment using UnitCommitment
using JuMP using JuMP
using Cbc using HiGHS
using JSON using JSON
import UnitCommitment: import UnitCommitment:
ArrCon2000, ArrCon2000,
@ -16,7 +16,8 @@ import UnitCommitment:
MorLatRam2013, MorLatRam2013,
PanGua2016, PanGua2016,
XavQiuWanThi2019, XavQiuWanThi2019,
WanHob2016 WanHob2016,
PhaseAngleFormulation
function _test( function _test(
formulation::Formulation; formulation::Formulation;
@ -24,11 +25,12 @@ function _test(
dump::Bool = false, dump::Bool = false,
)::Nothing )::Nothing
for instance_name in instances for instance_name in instances
instance = UnitCommitment.read(fixture("$(instance_name).json.gz")) # instance = UnitCommitment.read(fixture("$(instance_name).json.gz"))
instance = UnitCommitment.read(fixture("$(instance_name).json"))
model = UnitCommitment.build_model( model = UnitCommitment.build_model(
instance = instance, instance = instance,
formulation = formulation, formulation = formulation,
optimizer = Cbc.Optimizer, optimizer = HiGHS.Optimizer,
variable_names = true, variable_names = true,
) )
set_silent(model) set_silent(model)
@ -82,5 +84,11 @@ function model_formulations_test()
instances = ["case14-flex"], instances = ["case14-flex"],
) )
end end
@testset "Planning" begin
_test(
Formulation(transmission=PhaseAngleFormulation()),
instances = ["garver6"],
)
end
end end
end end

Loading…
Cancel
Save