From 2a6c206e08d7259a8458b65d2034053dd9a6a890 Mon Sep 17 00:00:00 2001 From: Jun He Date: Thu, 30 Mar 2023 23:19:24 -0400 Subject: [PATCH] updated LMP for UC scenario --- docs/src/usage.md | 9 ++--- src/lmp/aelmp.jl | 35 ++++++++++++------- src/lmp/conventional.jl | 8 ++--- test/lmp/aelmp_test.jl | 4 +-- .../lmp/{lmp_test.jl => conventional_test.jl} | 32 ++++++++--------- test/runtests.jl | 2 +- 6 files changed, 50 insertions(+), 40 deletions(-) rename test/lmp/{lmp_test.jl => conventional_test.jl} (61%) diff --git a/docs/src/usage.md b/docs/src/usage.md index 08fe545..aababa0 100644 --- a/docs/src/usage.md +++ b/docs/src/usage.md @@ -177,8 +177,8 @@ lmp = UnitCommitment.compute_lmp( ) # Access the LMPs -# Example: "b1" is the bus name, 1 is the first time slot -@show lmp["b1", 1] +# Example: "s1" is the scenario name, "b1" is the bus name, 1 is the first time slot +@show lmp["s1","b1", 1] ``` ### Approximate Extended LMPs @@ -220,6 +220,7 @@ aelmp = UnitCommitment.compute_lmp( ) # Access the AELMPs -# Example: "b1" is the bus name, 1 is the first time slot -@show aelmp["b1", 1] +# Example: "s1" is the scenario name, "b1" is the bus name, 1 is the first time slot +# Note: although scenario is supported, the query still keeps the scenario keys for consistency. +@show aelmp["s1", "b1", 1] ``` \ No newline at end of file diff --git a/src/lmp/aelmp.jl b/src/lmp/aelmp.jl index 0601a62..8b4e44c 100644 --- a/src/lmp/aelmp.jl +++ b/src/lmp/aelmp.jl @@ -26,6 +26,7 @@ WARNING: This approximation method is not fully developed. The implementation is 1. It only supports Fast Start resources. More specifically, the minimum up/down time has to be zero. 2. The method does NOT support time-varying start-up costs. 3. An asset is considered offline if it is never on throughout all time periods. +4. The method does NOT support multiple scenarios. Arguments --------- @@ -71,19 +72,20 @@ aelmp = UnitCommitment.compute_lmp( ) # Access the AELMPs -# Example: "b1" is the bus name, 1 is the first time slot -@show aelmp["b1", 1] +# Example: "s1" is the scenario name, "b1" is the bus name, 1 is the first time slot +# Note: although scenario is supported, the query still keeps the scenario keys for consistency. +@show aelmp["s1", "b1", 1] ``` """ function compute_lmp( model::JuMP.Model, method::AELMP; optimizer, -)::OrderedDict{Tuple{String,Int},Float64} +)::OrderedDict{Tuple{String,String,Int},Float64} @info "Building the approximation model..." instance = deepcopy(model[:instance]) _aelmp_check_parameters(instance, model, method) - _modify_instance!(instance, model, method) + _modify_scenario!(instance.scenarios[1], model, method) # prepare the result dictionary and solve the model elmp = OrderedDict() @@ -116,6 +118,13 @@ function _aelmp_check_parameters( model::JuMP.Model, method::AELMP, ) + # CHECK: model cannot have multiple scenarios + if length(instance.scenarios) > 1 + error( + "The method does NOT support multiple scenarios.", + ) + end + sc = instance.scenarios[1] # CHECK: model must be solved if allow_offline_participation=false if !method.allow_offline_participation if isnothing(model) || !has_values(model) @@ -124,7 +133,7 @@ function _aelmp_check_parameters( ) end end - all_units = instance.units + all_units = sc.units # CHECK: model cannot handle non-fast-starts (MISO Phase I: can ONLY solve fast-starts) if any(u -> u.min_uptime > 1 || u.min_downtime > 1, all_units) error( @@ -143,19 +152,19 @@ function _aelmp_check_parameters( end end -function _modify_instance!( - instance::UnitCommitmentInstance, +function _modify_scenario!( + sc::UnitCommitmentScenario, model::JuMP.Model, method::AELMP, ) - # this function modifies the instance units (generators) + # this function modifies the sc units (generators) if !method.allow_offline_participation # 1. remove (if NOT allowing) the offline generators units_to_remove = [] - for unit in instance.units + for unit in sc.units # remove based on the solved UC model result # remove the unit if it is never on - if all(t -> value(model[:is_on][unit.name, t]) == 0, instance.time) + if all(t -> value(model[:is_on][unit.name, t]) == 0, sc.time) # unregister from the bus filter!(x -> x.name != unit.name, unit.bus.units) # unregister from the reserve @@ -167,10 +176,10 @@ function _modify_instance!( end end # unregister the units from the remove list - filter!(x -> !(x.name in units_to_remove), instance.units) + filter!(x -> !(x.name in units_to_remove), sc.units) end - for unit in instance.units + for unit in sc.units # 2. set min generation requirement to 0 by adding 0 to production curve and cost # min_power & min_costs are vectors with dimension T if unit.min_power[1] != 0 @@ -200,5 +209,5 @@ function _modify_instance!( unit.startup_categories = StartupCategory[StartupCategory(0, first_startup_cost)] end - return instance.units_by_name = Dict(g.name => g for g in instance.units) + return sc.units_by_name = Dict(g.name => g for g in sc.units) end diff --git a/src/lmp/conventional.jl b/src/lmp/conventional.jl index 005cf60..38d07c5 100644 --- a/src/lmp/conventional.jl +++ b/src/lmp/conventional.jl @@ -9,7 +9,7 @@ using JuMP model::JuMP.Model, method::ConventionalLMP; optimizer, - )::OrderedDict{Tuple{String,Int},Float64} + )::OrderedDict{Tuple{String,String,Int},Float64} Calculates conventional locational marginal prices of the given unit commitment instance. Returns a dictionary mapping `(bus_name, time)` to the marginal price. @@ -55,15 +55,15 @@ lmp = UnitCommitment.compute_lmp( ) # Access the LMPs -# Example: "b1" is the bus name, 1 is the first time slot -@show lmp["b1", 1] +# Example: "s1" is the scenario name, "b1" is the bus name, 1 is the first time slot +@show lmp["s1", "b1", 1] ``` """ function compute_lmp( model::JuMP.Model, ::ConventionalLMP; optimizer, -)::OrderedDict{Tuple{String,Int},Float64} +)::OrderedDict{Tuple{String,String,Int},Float64} if !has_values(model) error("The UC model must be solved before calculating the LMPs.") end diff --git a/test/lmp/aelmp_test.jl b/test/lmp/aelmp_test.jl index 29a3194..5f236da 100644 --- a/test/lmp/aelmp_test.jl +++ b/test/lmp/aelmp_test.jl @@ -20,7 +20,7 @@ import UnitCommitment: AELMP # policy 1: allow offlines; consider startups aelmp_1 = UnitCommitment.compute_lmp(model, AELMP(), optimizer = HiGHS.Optimizer) - @test aelmp_1["B1", 1] ≈ 231.7 atol = 0.1 + @test aelmp_1["s1","B1", 1] ≈ 231.7 atol = 0.1 # policy 2: do not allow offlines; but consider startups aelmp_2 = UnitCommitment.compute_lmp( @@ -31,5 +31,5 @@ import UnitCommitment: AELMP ), optimizer = HiGHS.Optimizer, ) - @test aelmp_2["B1", 1] ≈ 274.3 atol = 0.1 + @test aelmp_2["s1","B1", 1] ≈ 274.3 atol = 0.1 end diff --git a/test/lmp/lmp_test.jl b/test/lmp/conventional_test.jl similarity index 61% rename from test/lmp/lmp_test.jl rename to test/lmp/conventional_test.jl index ddd3411..d78d443 100644 --- a/test/lmp/lmp_test.jl +++ b/test/lmp/conventional_test.jl @@ -5,7 +5,7 @@ using UnitCommitment, Cbc, HiGHS, JuMP import UnitCommitment: ConventionalLMP -function solve_lmp_testcase(path::String) +function solve_conventional_testcase(path::String) instance = UnitCommitment.read(path) model = UnitCommitment.build_model( instance = instance, @@ -22,30 +22,30 @@ function solve_lmp_testcase(path::String) return lmp end -@testset "lmp" begin +@testset "conventional" begin # instance 1 path = "$FIXTURES/lmp_simple_test_1.json.gz" - lmp = solve_lmp_testcase(path) - @test lmp["A", 1] == 50.0 - @test lmp["B", 1] == 50.0 + lmp = solve_conventional_testcase(path) + @test lmp["s1", "A", 1] == 50.0 + @test lmp["s1", "B", 1] == 50.0 # instance 2 path = "$FIXTURES/lmp_simple_test_2.json.gz" - lmp = solve_lmp_testcase(path) - @test lmp["A", 1] == 50.0 - @test lmp["B", 1] == 60.0 + lmp = solve_conventional_testcase(path) + @test lmp["s1", "A", 1] == 50.0 + @test lmp["s1", "B", 1] == 60.0 # instance 3 path = "$FIXTURES/lmp_simple_test_3.json.gz" - lmp = solve_lmp_testcase(path) - @test lmp["A", 1] == 50.0 - @test lmp["B", 1] == 70.0 - @test lmp["C", 1] == 100.0 + lmp = solve_conventional_testcase(path) + @test lmp["s1","A", 1] == 50.0 + @test lmp["s1","B", 1] == 70.0 + @test lmp["s1","C", 1] == 100.0 # instance 4 path = "$FIXTURES/lmp_simple_test_4.json.gz" - lmp = solve_lmp_testcase(path) - @test lmp["A", 1] == 50.0 - @test lmp["B", 1] == 70.0 - @test lmp["C", 1] == 90.0 + lmp = solve_conventional_testcase(path) + @test lmp["s1","A", 1] == 50.0 + @test lmp["s1","B", 1] == 70.0 + @test lmp["s1","C", 1] == 90.0 end diff --git a/test/runtests.jl b/test/runtests.jl index 4203495..08ade97 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -40,7 +40,7 @@ FIXTURES = "$(@__DIR__)/fixtures" include("validation/repair_test.jl") end @testset "lmp" begin - include("lmp/lmp_test.jl") + include("lmp/conventional_test.jl") include("lmp/aelmp_test.jl") end end