diff --git a/NEWS.md b/NEWS.md index 5fa0700..0dc6d08 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,13 @@ # Release notes +## Version 0.10.2 (2026-06-09) + +### Bug fixes + +* Fixed a bug for using `StrategicProfile` in the field `opex_fixed` of a node and `TwoLevelTree` as time structure. +* Fixed a bug for using `StrategicProfile` in the field `emission_limit` of a modeltype and `TwoLevelTree` as time structure. +* Fixed bug in function `check_strategic_profile` when utilizing `StrategicStochasticProfile`. + ## Version 0.10.1 (2026-04-14) ### Minor updates diff --git a/Project.toml b/Project.toml index c008a8d..6c3cfd8 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "EnergyModelsBase" uuid = "5d7e687e-f956-46f3-9045-6f5a5fd49f50" authors = ["Lars Hellemo , Julian Straus "] -version = "0.10.1" +version = "0.10.2" [deps] JuMP = "4076af6c-e467-56ae-b986-b466b2749572" diff --git a/src/checks.jl b/src/checks.jl index 07e5b01..1e337aa 100644 --- a/src/checks.jl +++ b/src/checks.jl @@ -296,7 +296,6 @@ Checks the `modeltype` . """ function check_model(case, modeltype::EnergyModel, check_timeprofiles::Bool) 𝒯 = get_time_struct(case) - 𝒯ᴵⁿᵛ = strategic_periods(𝒯) # Check for inclusion of all emission resources for p ∈ get_products(case) @@ -310,27 +309,21 @@ function check_model(case, modeltype::EnergyModel, check_timeprofiles::Bool) end for p ∈ keys(emission_limit(modeltype)) - em_limit = emission_limit(modeltype, p) - # Check for the strategic periods - if isa(em_limit, StrategicProfile) && check_timeprofiles - @assert_or_log( - length(em_limit.vals) == length(𝒯ᴵⁿᵛ), - "The timeprofile provided for resource `" * - string(p) * - "` in the field " * - "`emission_limit` does not match the strategic structure." - ) - end - # Check for potential indexing problems + em_limit = emission_limit(modeltype, p) message = "are not allowed for the resource `" * string(p) * "` in the dictionary " * "`emission_limit`." check_strategic_profile(em_limit, message) + + # Check for the strategic periods + check_timeprofiles || continue + check_profile("emission_limit[" * string(p) * "]", em_limit, 𝒯) end + # Check for the strategic periods for p ∈ keys(emission_price(modeltype)) em_price = emission_price(modeltype, p) check_timeprofiles || continue @@ -662,6 +655,13 @@ function check_strategic_profile(time_profile::TimeProfile, message::String) for l1_profile ∈ time_profile.vals sub_msg = "in strategic profiles " * message bool_sp = check_strat_sub_profile(l1_profile, sub_msg, bool_sp) + !bool_sp && break + end + elseif isa(time_profile, StrategicStochasticProfile) + for sp_array ∈ time_profile.vals, l1_profile ∈ sp_array + sub_msg = "in strategic stochastic profiles " * message + bool_sp = check_strat_sub_profile(l1_profile, sub_msg, bool_sp) + !bool_sp && break end end @@ -1017,14 +1017,6 @@ returns a `TimeProfile`. periods. """ function check_fixed_opex(n, 𝒯ᴵⁿᵛ, check_timeprofiles::Bool) - if isa(opex_fixed(n), StrategicProfile) && check_timeprofiles - @assert_or_log( - length(opex_fixed(n).vals) == length(𝒯ᴵⁿᵛ), - "The timeprofile provided for the field `opex_fixed` does not match the " * - "strategic structure." - ) - end - # Check for potential indexing problems message = "are not allowed for the field `opex_fixed`." bool_sp = check_strategic_profile(opex_fixed(n), message) diff --git a/test/test_checks.jl b/test/test_checks.jl index 0c8ef8c..b7ef269 100644 --- a/test/test_checks.jl +++ b/test/test_checks.jl @@ -462,6 +462,9 @@ end StrategicProfile([OperationalProfile([5])]), StrategicProfile([ScenarioProfile([5])]), StrategicProfile([RepresentativeProfile([5])]), + StrategicStochasticProfile([[OperationalProfile([5])]]), + StrategicStochasticProfile([[ScenarioProfile([5])]]), + StrategicStochasticProfile([[RepresentativeProfile([5])]]), ] for tp ∈ profiles @test_throws AssertionError EMB.check_strategic_profile(tp, "") @@ -514,7 +517,6 @@ end end @testset "Checks - Nodes" begin - # Resources used in the checks NG = ResourceEmit("NG", 0.2) Power = ResourceCarrier("Power", 0.0) @@ -522,7 +524,7 @@ end aux = ResourceCarrier("aux", 0.0) # Function for setting up the system for testing `Sink` and `Source` - function simple_graph(; + function check_graph_src_snk(; src_cap::TimeProfile = FixedProfile(10), src_opex_var::TimeProfile = FixedProfile(10), src_opex_fixed::TimeProfile = FixedProfile(0), @@ -530,10 +532,9 @@ end snk_cap::TimeProfile = OperationalProfile([6, 8, 10, 6, 8]), snk_pen::Dict = Dict(:surplus => FixedProfile(4), :deficit => FixedProfile(10)), snk_input::Dict = Dict(Power => 1), + T = TwoLevel(2, 2, SimpleTimes(5, 2); op_per_strat = 10), ) resources = [Power, CO2] - ops = SimpleTimes(5, 2) - T = TwoLevel(2, 2, ops; op_per_strat = 10) source = RefSource( @@ -566,22 +567,38 @@ end # Sink used in the analysis # Test that a wrong capacity is caught by the checks. src_cap = FixedProfile(-4) - @test_throws AssertionError simple_graph(;src_cap) + @test_throws AssertionError check_graph_src_snk(;src_cap) # Test that a wrong output dictionary is caught by the checks. src_output = Dict(Power => -1) - @test_throws AssertionError simple_graph(;src_output) + @test_throws AssertionError check_graph_src_snk(;src_output) # Test that a wrong fixed OPEX is caught by the checks. src_opex_fixed = FixedProfile(-5) - @test_throws AssertionError simple_graph(;src_opex_fixed) + @test_throws AssertionError check_graph_src_snk(; src_opex_fixed) - # Test that a wrong profile for fixed OPEX is caught by the checks. + # Test that a wrong profile for fixed OPEX is caught by the checks both with a + # `TwoLevelTree` and `TwoLevel` time structure # - check_fixed_opex(n::Node, 𝒯ᴵⁿᵛ, check_timeprofiles::Bool) src_opex_fixed = StrategicProfile([1]) - @test_throws AssertionError simple_graph(;src_opex_fixed) + @test_throws AssertionError check_graph_src_snk(; src_opex_fixed) src_opex_fixed = OperationalProfile([1]) - @test_throws AssertionError simple_graph(;src_opex_fixed) + @test_throws AssertionError check_graph_src_snk(; src_opex_fixed) + src_opex_fixed = StrategicProfile([OperationalProfile([1]), OperationalProfile([1])]) + @test_throws AssertionError check_graph_src_snk(; src_opex_fixed) + + T = TwoLevelTree(2, [2], SimpleTimes(5, 1); op_per_strat = 10.) + src_opex_fixed = StrategicProfile([1]) + @test_throws AssertionError check_graph_src_snk(; src_opex_fixed, T) + src_opex_fixed = StrategicProfile([1, 2, 3]) + @test_throws AssertionError check_graph_src_snk(; src_opex_fixed, T) + src_opex_fixed = OperationalProfile([1]) + @test_throws AssertionError check_graph_src_snk(; src_opex_fixed, T) + src_opex_fixed = StrategicStochasticProfile([ + [OperationalProfile([1])], + [OperationalProfile([1]), OperationalProfile([1])], + ]) + @test_throws AssertionError check_graph_src_snk(; src_opex_fixed, T) end # Test that the fields of a Sink are correctly checked @@ -589,16 +606,16 @@ end @testset "Sink" begin # Test that an inconsistent Sink.penalty dictionaries is caught by the checks. snk_pen = Dict(:surplus => FixedProfile(4), :def => FixedProfile(2)) - @test_throws AssertionError simple_graph(;snk_pen) + @test_throws AssertionError check_graph_src_snk(;snk_pen) # The penalties in this Sink node lead to an infeasible optimum. Test that the # checks forbids it. snk_pen = Dict(:surplus => FixedProfile(-4), :deficit => FixedProfile(2)) - @test_throws AssertionError simple_graph(;snk_pen) + @test_throws AssertionError check_graph_src_snk(;snk_pen) # Check that a wrong capacity in a sink is caught by the checks. snk_cap = OperationalProfile(-[6, 8, 10, 6, 8]) - @test_throws AssertionError simple_graph(;snk_cap) + @test_throws AssertionError check_graph_src_snk(;snk_cap) end # Function for setting up the system for testing a `NetworkNode` diff --git a/test/test_investments.jl b/test/test_investments.jl index 174c4f0..ff1642d 100644 --- a/test/test_investments.jl +++ b/test/test_investments.jl @@ -227,8 +227,9 @@ using EnergyModelsInvestments # Test results # (-724 compared to 0.5.x as RefStorage as emission source does not require a charge # capacity any longer in 0.7.x) - # (-10736 compared to 0.9.x due to the potential of early retirment) - @test round(objective_value(m)) ≈ -313360.0 + # (-10736 compared to 0.9.x due to the potential of early retirement) + # (-16689 compared to 10.1.x due to the bugfix 0.9.1 in EMI) + @test round(objective_value(m)) ≈ -296671.0 # Test that investments are happening 𝒯ᴵⁿᵛ = strategic_periods(get_time_struct(case)) @@ -247,7 +248,6 @@ using EnergyModelsInvestments @test sum( sum(value.(m[:stor_charge_add][n, t_inv]) > 0 for n ∈ 𝒩ᶜʰᵃʳᵍᵉ) for t_inv ∈ 𝒯ᴵⁿᵛ) > 0 - end @testset "Link - OPEX and investments" begin