updated LMP for UC scenario

pull/26/head
Jun He 3 years ago
parent 30a4284119
commit 2a6c206e08

@ -177,8 +177,8 @@ lmp = UnitCommitment.compute_lmp(
) )
# Access the LMPs # Access the LMPs
# Example: "b1" is the bus name, 1 is the first time slot # Example: "s1" is the scenario name, "b1" is the bus name, 1 is the first time slot
@show lmp["b1", 1] @show lmp["s1","b1", 1]
``` ```
### Approximate Extended LMPs ### Approximate Extended LMPs
@ -220,6 +220,7 @@ aelmp = UnitCommitment.compute_lmp(
) )
# Access the AELMPs # Access the AELMPs
# Example: "b1" is the bus name, 1 is the first time slot # Example: "s1" is the scenario name, "b1" is the bus name, 1 is the first time slot
@show aelmp["b1", 1] # Note: although scenario is supported, the query still keeps the scenario keys for consistency.
@show aelmp["s1", "b1", 1]
``` ```

@ -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. 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. 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. 3. An asset is considered offline if it is never on throughout all time periods.
4. The method does NOT support multiple scenarios.
Arguments Arguments
--------- ---------
@ -71,19 +72,20 @@ aelmp = UnitCommitment.compute_lmp(
) )
# Access the AELMPs # Access the AELMPs
# Example: "b1" is the bus name, 1 is the first time slot # Example: "s1" is the scenario name, "b1" is the bus name, 1 is the first time slot
@show aelmp["b1", 1] # Note: although scenario is supported, the query still keeps the scenario keys for consistency.
@show aelmp["s1", "b1", 1]
``` ```
""" """
function compute_lmp( function compute_lmp(
model::JuMP.Model, model::JuMP.Model,
method::AELMP; method::AELMP;
optimizer, optimizer,
)::OrderedDict{Tuple{String,Int},Float64} )::OrderedDict{Tuple{String,String,Int},Float64}
@info "Building the approximation model..." @info "Building the approximation model..."
instance = deepcopy(model[:instance]) instance = deepcopy(model[:instance])
_aelmp_check_parameters(instance, model, method) _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 # prepare the result dictionary and solve the model
elmp = OrderedDict() elmp = OrderedDict()
@ -116,6 +118,13 @@ function _aelmp_check_parameters(
model::JuMP.Model, model::JuMP.Model,
method::AELMP, 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 # CHECK: model must be solved if allow_offline_participation=false
if !method.allow_offline_participation if !method.allow_offline_participation
if isnothing(model) || !has_values(model) if isnothing(model) || !has_values(model)
@ -124,7 +133,7 @@ function _aelmp_check_parameters(
) )
end end
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) # 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) if any(u -> u.min_uptime > 1 || u.min_downtime > 1, all_units)
error( error(
@ -143,19 +152,19 @@ function _aelmp_check_parameters(
end end
end end
function _modify_instance!( function _modify_scenario!(
instance::UnitCommitmentInstance, sc::UnitCommitmentScenario,
model::JuMP.Model, model::JuMP.Model,
method::AELMP, method::AELMP,
) )
# this function modifies the instance units (generators) # this function modifies the sc units (generators)
if !method.allow_offline_participation if !method.allow_offline_participation
# 1. remove (if NOT allowing) the offline generators # 1. remove (if NOT allowing) the offline generators
units_to_remove = [] units_to_remove = []
for unit in instance.units for unit in sc.units
# remove based on the solved UC model result # remove based on the solved UC model result
# remove the unit if it is never on # 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 # unregister from the bus
filter!(x -> x.name != unit.name, unit.bus.units) filter!(x -> x.name != unit.name, unit.bus.units)
# unregister from the reserve # unregister from the reserve
@ -167,10 +176,10 @@ function _modify_instance!(
end end
end end
# unregister the units from the remove list # 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 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 # 2. set min generation requirement to 0 by adding 0 to production curve and cost
# min_power & min_costs are vectors with dimension T # min_power & min_costs are vectors with dimension T
if unit.min_power[1] != 0 if unit.min_power[1] != 0
@ -200,5 +209,5 @@ function _modify_instance!(
unit.startup_categories = unit.startup_categories =
StartupCategory[StartupCategory(0, first_startup_cost)] StartupCategory[StartupCategory(0, first_startup_cost)]
end 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 end

@ -9,7 +9,7 @@ using JuMP
model::JuMP.Model, model::JuMP.Model,
method::ConventionalLMP; method::ConventionalLMP;
optimizer, optimizer,
)::OrderedDict{Tuple{String,Int},Float64} )::OrderedDict{Tuple{String,String,Int},Float64}
Calculates conventional locational marginal prices of the given unit commitment Calculates conventional locational marginal prices of the given unit commitment
instance. Returns a dictionary mapping `(bus_name, time)` to the marginal price. instance. Returns a dictionary mapping `(bus_name, time)` to the marginal price.
@ -55,15 +55,15 @@ lmp = UnitCommitment.compute_lmp(
) )
# Access the LMPs # Access the LMPs
# Example: "b1" is the bus name, 1 is the first time slot # Example: "s1" is the scenario name, "b1" is the bus name, 1 is the first time slot
@show lmp["b1", 1] @show lmp["s1", "b1", 1]
``` ```
""" """
function compute_lmp( function compute_lmp(
model::JuMP.Model, model::JuMP.Model,
::ConventionalLMP; ::ConventionalLMP;
optimizer, optimizer,
)::OrderedDict{Tuple{String,Int},Float64} )::OrderedDict{Tuple{String,String,Int},Float64}
if !has_values(model) if !has_values(model)
error("The UC model must be solved before calculating the LMPs.") error("The UC model must be solved before calculating the LMPs.")
end end

@ -20,7 +20,7 @@ import UnitCommitment: AELMP
# policy 1: allow offlines; consider startups # policy 1: allow offlines; consider startups
aelmp_1 = aelmp_1 =
UnitCommitment.compute_lmp(model, AELMP(), optimizer = HiGHS.Optimizer) 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 # policy 2: do not allow offlines; but consider startups
aelmp_2 = UnitCommitment.compute_lmp( aelmp_2 = UnitCommitment.compute_lmp(
@ -31,5 +31,5 @@ import UnitCommitment: AELMP
), ),
optimizer = HiGHS.Optimizer, 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 end

@ -5,7 +5,7 @@
using UnitCommitment, Cbc, HiGHS, JuMP using UnitCommitment, Cbc, HiGHS, JuMP
import UnitCommitment: ConventionalLMP import UnitCommitment: ConventionalLMP
function solve_lmp_testcase(path::String) function solve_conventional_testcase(path::String)
instance = UnitCommitment.read(path) instance = UnitCommitment.read(path)
model = UnitCommitment.build_model( model = UnitCommitment.build_model(
instance = instance, instance = instance,
@ -22,30 +22,30 @@ function solve_lmp_testcase(path::String)
return lmp return lmp
end end
@testset "lmp" begin @testset "conventional" begin
# instance 1 # instance 1
path = "$FIXTURES/lmp_simple_test_1.json.gz" path = "$FIXTURES/lmp_simple_test_1.json.gz"
lmp = solve_lmp_testcase(path) lmp = solve_conventional_testcase(path)
@test lmp["A", 1] == 50.0 @test lmp["s1", "A", 1] == 50.0
@test lmp["B", 1] == 50.0 @test lmp["s1", "B", 1] == 50.0
# instance 2 # instance 2
path = "$FIXTURES/lmp_simple_test_2.json.gz" path = "$FIXTURES/lmp_simple_test_2.json.gz"
lmp = solve_lmp_testcase(path) lmp = solve_conventional_testcase(path)
@test lmp["A", 1] == 50.0 @test lmp["s1", "A", 1] == 50.0
@test lmp["B", 1] == 60.0 @test lmp["s1", "B", 1] == 60.0
# instance 3 # instance 3
path = "$FIXTURES/lmp_simple_test_3.json.gz" path = "$FIXTURES/lmp_simple_test_3.json.gz"
lmp = solve_lmp_testcase(path) lmp = solve_conventional_testcase(path)
@test lmp["A", 1] == 50.0 @test lmp["s1","A", 1] == 50.0
@test lmp["B", 1] == 70.0 @test lmp["s1","B", 1] == 70.0
@test lmp["C", 1] == 100.0 @test lmp["s1","C", 1] == 100.0
# instance 4 # instance 4
path = "$FIXTURES/lmp_simple_test_4.json.gz" path = "$FIXTURES/lmp_simple_test_4.json.gz"
lmp = solve_lmp_testcase(path) lmp = solve_conventional_testcase(path)
@test lmp["A", 1] == 50.0 @test lmp["s1","A", 1] == 50.0
@test lmp["B", 1] == 70.0 @test lmp["s1","B", 1] == 70.0
@test lmp["C", 1] == 90.0 @test lmp["s1","C", 1] == 90.0
end end

@ -40,7 +40,7 @@ FIXTURES = "$(@__DIR__)/fixtures"
include("validation/repair_test.jl") include("validation/repair_test.jl")
end end
@testset "lmp" begin @testset "lmp" begin
include("lmp/lmp_test.jl") include("lmp/conventional_test.jl")
include("lmp/aelmp_test.jl") include("lmp/aelmp_test.jl")
end end
end end

Loading…
Cancel
Save