From ef3e9fa78258ce59b2c240d475bf890dc863b5b2 Mon Sep 17 00:00:00 2001 From: Julian Straus Date: Wed, 10 Jun 2026 09:39:20 +0200 Subject: [PATCH 1/2] Added boolean to remove unnecessary checks --- src/checks.jl | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/checks.jl b/src/checks.jl index 1e337aa..5cf0dbf 100644 --- a/src/checks.jl +++ b/src/checks.jl @@ -347,25 +347,28 @@ function check_time_structure(x::AbstractElement, 𝒯) end """ - check_profile(fieldname, value::TimeProfile, 𝒯::TwoLevel) - check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevel) - check_profile(fieldname, value::StrategicStochasticProfile, 𝒯::TwoLevel) + check_profile(fieldname, value::TimeProfile, 𝒯::TwoLevel; bool::Bool=true) + check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevel; bool::Bool=true) + check_profile(fieldname, value::StrategicStochasticProfile, 𝒯::TwoLevel; bool::Bool=true) - check_profile(fieldname, value::TimeProfile, 𝒯::TwoLevelTree) - check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevelTree) - check_profile(fieldname, value::StrategicStochasticProfile, 𝒯::TwoLevelTree) + check_profile(fieldname, value::TimeProfile, 𝒯::TwoLevelTree; bool::Bool=true) + check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevelTree; bool::Bool=true) + check_profile(fieldname, value::StrategicStochasticProfile, 𝒯::TwoLevelTree; bool::Bool=true) Check that an individual `TimeProfile` corresponds to the time structure `𝒯`. The individual checks are depending on the profile type and the time structure. + +The key word argument `bool` is used to identify whether subprofiles should be checked +(`true` as default) or not (`false`). """ -function check_profile(fieldname, value::TimeProfile, 𝒯::TwoLevel) +function check_profile(fieldname, value::TimeProfile, 𝒯::TwoLevel; bool::Bool=true) 𝒯ᴵⁿᵛ = strategic_periods(𝒯) - for t_inv ∈ 𝒯ᴵⁿᵛ + bool && for t_inv ∈ 𝒯ᴵⁿᵛ p_msg = "strategic period $(t_inv.sp)" check_profile(fieldname, value, t_inv.operational, p_msg) end end -function check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevel) +function check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevel; bool::Bool=true) 𝒯ᴵⁿᵛ = strategic_periods(𝒯) len_vals = length(value.vals) @@ -380,7 +383,7 @@ function check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevel) @assert_or_log( len_vals == len_ts, "The `TimeProfile` of field `" * string(fieldname) * message ) - for t_inv ∈ 𝒯ᴵⁿᵛ + bool && for t_inv ∈ 𝒯ᴵⁿᵛ p_msg = "strategic period $(t_inv.sp)" check_profile( fieldname, @@ -390,7 +393,7 @@ function check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevel) ) end end -function check_profile(fieldname, value::StrategicStochasticProfile, 𝒯::TwoLevel) +function check_profile(fieldname, value::StrategicStochasticProfile, 𝒯::TwoLevel; bool::Bool=true) @warn( "Using `StrategicStochasticProfile` with `TwoLevel` is dangerous, " * "as it may lead to unexpected behaviour. " * @@ -401,14 +404,14 @@ function check_profile(fieldname, value::StrategicStochasticProfile, 𝒯::TwoLe prof = StrategicProfile([op_prof[1] for op_prof ∈ value.vals]) check_profile(fieldname, prof, 𝒯) end -function check_profile(fieldname, value::TimeProfile, 𝒯::TwoLevelTree) +function check_profile(fieldname, value::TimeProfile, 𝒯::TwoLevelTree; bool::Bool=true) 𝒯ᴵⁿᵛ = strategic_periods(𝒯) - for t_inv ∈ 𝒯ᴵⁿᵛ + bool && for t_inv ∈ 𝒯ᴵⁿᵛ p_msg = "branch $(t_inv.branch) in strategic period $(t_inv.sp)" check_profile(fieldname, value, t_inv.operational, p_msg) end end -function check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevelTree) +function check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevelTree; bool::Bool=true) 𝒯ˢˢᶜ = strategic_scenarios(𝒯) t_inv_vec = [] @@ -426,7 +429,7 @@ function check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevelTree) @assert_or_log( len_vals == len_ts, "The `TimeProfile` of field `" * string(fieldname) * message, ) - for t_inv ∈ 𝒯ᴵⁿᵛ + bool && for t_inv ∈ 𝒯ᴵⁿᵛ t_inv ∈ t_inv_vec && continue push!(t_inv_vec, t_inv) @@ -440,7 +443,7 @@ function check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevelTree) end end end -function check_profile(fieldname, value::StrategicStochasticProfile, 𝒯::TwoLevelTree) +function check_profile(fieldname, value::StrategicStochasticProfile, 𝒯::TwoLevelTree; bool::Bool=true) # Check for the number of strategic periods len_vals = length(value.vals) len_ts = n_strat_per(𝒯) @@ -475,7 +478,7 @@ function check_profile(fieldname, value::StrategicStochasticProfile, 𝒯::TwoLe end # Check the sub profiles - for t_inv ∈ strategic_periods(𝒯) + bool && for t_inv ∈ strategic_periods(𝒯) p_msg = "branch $(t_inv.branch) in strategic period $(t_inv.sp)" sp_prof = value.vals[minimum([t_inv.sp, length(value.vals)])] check_profile( From 8c41d98c55eb79232a25cb8d9aae02f00c236532 Mon Sep 17 00:00:00 2001 From: Julian Straus Date: Wed, 10 Jun 2026 10:32:35 +0200 Subject: [PATCH 2/2] Fixed the problems for the investment data --- NEWS.md | 6 +++ Project.toml | 2 +- ext/EMIExt/checks.jl | 46 +++++++++------------ test/test_investments.jl | 87 +++++++++++++++++++--------------------- 4 files changed, 69 insertions(+), 72 deletions(-) diff --git a/NEWS.md b/NEWS.md index 0dc6d08..abb0612 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,11 @@ # Release notes +## Version 0.10.3 (2026-06-10) + +### Bug fixes + +* Fixed addition bugs with `StrategicProfile` and `TwoLevelTree` in the checks for the investment data. + ## Version 0.10.2 (2026-06-09) ### Bug fixes diff --git a/Project.toml b/Project.toml index 6c3cfd8..c7df5d9 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.2" +version = "0.10.3" [deps] JuMP = "4076af6c-e467-56ae-b986-b466b2749572" diff --git a/ext/EMIExt/checks.jl b/ext/EMIExt/checks.jl index 577efb0..369a051 100644 --- a/ext/EMIExt/checks.jl +++ b/ext/EMIExt/checks.jl @@ -81,8 +81,8 @@ Performs various checks on investment data introduced within EnergyModelsInvestm ## Checks - For each field with `TimeProfile`: - - If the `TimeProfile` is a `StrategicProfile`, it will check that the profile is in - accordance with the `TimeStructure` + - If the `TimeProfile` is a `StrategicProfile` or `StrategicStochasticProfile`, it will + check that the profile is in accordance with the `TimeStructure` - `TimeProfile`s in `InvestmentData` cannot include `OperationalProfile`, `RepresentativeProfile`, or `ScenarioProfile` as this is not allowed through indexing on the `TimeProfile`. @@ -101,45 +101,39 @@ function check_inv_data( check_timeprofiles::Bool, ) 𝒯ᴵⁿᵛ = strategic_periods(𝒯) - bool_sp = true + bool = false # Boolean for subprofile checks + bool_sp = true # Boolean # Check on the individual time profiles for field_name ∈ fieldnames(typeof(inv_data)) - time_profile = getfield(inv_data, field_name) - if isa(time_profile, Union{Investment,LifetimeMode}) - for sub_field_name ∈ fieldnames(typeof(time_profile)) - sub_time_profile = getfield(time_profile, sub_field_name) + tp = getfield(inv_data, field_name) + if isa(tp, Union{Investment,LifetimeMode}) + for sub_field_name ∈ fieldnames(typeof(tp)) + stp = getfield(tp, sub_field_name) submessage = "are not allowed for the field `" * String(sub_field_name) * "` of the mode `" * String(field_name) * "` in the investment data" * message * "." - if isa(sub_time_profile, StrategicProfile) && check_timeprofiles - @assert_or_log( - length(sub_time_profile.vals) == length(𝒯ᴵⁿᵛ), - "Field `" * string(sub_field_name) * - "` does not match the strategic structure." - ) + if (isa(stp, StrategicProfile) || isa(stp, StrategicStochasticProfile)) && + check_timeprofiles + EMB.check_profile(string(sub_field_name), stp, 𝒯; bool) end - EMB.check_strategic_profile(sub_time_profile, submessage) + EMB.check_strategic_profile(stp, submessage) end end - !isa(time_profile, TimeProfile) && continue - isa(time_profile, FixedProfile) && continue + (!isa(tp, TimeProfile) || isa(tp, FixedProfile)) && continue submessage = "are not allowed for the field `" * String(field_name) * "` in the investment data" * message * "." - if isa(time_profile, StrategicProfile) && check_timeprofiles - @assert_or_log( - length(time_profile.vals) == length(𝒯ᴵⁿᵛ), - "Field `" * string(field_name) * "` does not match the strategic " * - "structure in the investment data" * message * "." - ) + if (isa(tp, StrategicProfile) || isa(tp, StrategicStochasticProfile)) && + check_timeprofiles + EMB.check_profile(string(field_name), tp, 𝒯; bool) end - if field_name == :initial - bool_sp = EMB.check_strategic_profile(time_profile, submessage) + if field_name == :initial || field_name == :max_inst + bool_sp *= EMB.check_strategic_profile(tp, submessage) else - EMB.check_strategic_profile(time_profile, submessage) + EMB.check_strategic_profile(tp, submessage) end end @@ -156,7 +150,7 @@ function check_inv_data( submessage = "are not allowed for the capacity of the investment data " * message * ", if investments are allowed and the chosen investment type is `NoStartInvData`." - bool_sp = EMB.check_strategic_profile(capacity_profile, submessage) + bool_sp *= EMB.check_strategic_profile(capacity_profile, submessage) if bool_sp @assert_or_log( all(capacity_profile[t_inv] ≤ EMI.max_installed(inv_data, t_inv) for t_inv ∈ 𝒯ᴵⁿᵛ), diff --git a/test/test_investments.jl b/test/test_investments.jl index ff1642d..3d12a3e 100644 --- a/test/test_investments.jl +++ b/test/test_investments.jl @@ -380,17 +380,19 @@ EMB.TEST_ENV = true # - EMB.check_node_data(n::EMB.Node, data::InvestmentData, 𝒯, modeltype::AbstractInvestmentModel) @testset "SingleInvData" begin - function build_simple_graph(; + function check_graph_inv_node(; cap = FixedProfile(0), + max_inst = FixedProfile(30), min_add = FixedProfile(0), max_add = FixedProfile(10), inv_data = nothing, + T = TwoLevel(4, 10, SimpleTimes(4, 1)) ) if isnothing(inv_data) inv_data = [ SingleInvData( FixedProfile(1000), # capex [€/kW] - FixedProfile(30), # max installed capacity [kW] + max_inst, # max installed capacity [kW] ContinuousInvestment(min_add, max_add), # investment mode ), ] @@ -416,7 +418,6 @@ EMB.TEST_ENV = true ) nodes = [source, sink] links = [Direct("scr-sink", nodes[1], nodes[2], Linear())] - T = TwoLevel(4, 10, SimpleTimes(4, 1)) case = Case(T, products, [nodes, links], [[get_nodes, get_links]]) em_limits = Dict(CO2 => StrategicProfile([450, 400, 350, 300])) @@ -439,62 +440,58 @@ EMB.TEST_ENV = true ContinuousInvestment(FixedProfile(0), FixedProfile(20)), # investment mode ), ] - @test_throws AssertionError build_simple_graph(;inv_data) + @test_throws AssertionError check_graph_inv_node(;inv_data) - # Check that we receive an error if the profiles are wrong - rprofile = RepresentativeProfile([FixedProfile(4)]) - scprofile = ScenarioProfile([FixedProfile(4)]) + # Check that we receive an error if the profiles are wrong both with a `TwoLevelTree` + # and `TwoLevel` time structure for the the sub fields oprofile = OperationalProfile(ones(4)) + profiles = [ + oprofile, + StrategicProfile([4]), + StrategicProfile([oprofile, oprofile, oprofile, oprofile]) + ] + T = TwoLevelTree(10, [2, 2, 1], SimpleTimes(4, 1)) - max_add = oprofile - @test_throws AssertionError build_simple_graph(;max_add) - max_add = scprofile - @test_throws AssertionError build_simple_graph(;max_add) - max_add = rprofile - @test_throws AssertionError build_simple_graph(;max_add) - max_add = StrategicProfile([4]) - @test_throws AssertionError build_simple_graph(;max_add) - - max_add = StrategicProfile([oprofile, oprofile, oprofile, oprofile]) - @test_throws AssertionError build_simple_graph(;max_add) - max_add = StrategicProfile([scprofile, scprofile, scprofile, scprofile]) - @test_throws AssertionError build_simple_graph(;max_add) - max_add = StrategicProfile([rprofile, rprofile, rprofile, rprofile]) - @test_throws AssertionError build_simple_graph(;max_add) + for tp ∈ profiles + @test_throws AssertionError check_graph_inv_node(; max_add=tp) + @test_throws AssertionError check_graph_inv_node(; max_inst=tp) + @test_throws AssertionError check_graph_inv_node(; max_add=tp, T) + @test_throws AssertionError check_graph_inv_node(; max_inst=tp, T) + end # Check that we receive an error if the capacity is an operational profile cap = OperationalProfile(ones(4)) - @test_throws AssertionError build_simple_graph(;cap) + @test_throws AssertionError check_graph_inv_node(;cap) inv_data = [SingleInvData( FixedProfile(1000), # capex [€/kW] FixedProfile(10), # max installed capacity [kW] cap, # initial capacity ContinuousInvestment(FixedProfile(0), FixedProfile(20)), # investment mode )] - @test_throws AssertionError build_simple_graph(;inv_data) + @test_throws AssertionError check_graph_inv_node(; inv_data) # Check that we receive an error if the initial capacity is higher than the # allowed maximum installed cap = FixedProfile(50) - @test_throws AssertionError build_simple_graph(;cap) + @test_throws AssertionError check_graph_inv_node(; cap) inv_data = [SingleInvData( FixedProfile(1000), # capex [€/kW] FixedProfile(10), # max installed capacity [kW] FixedProfile(50), # initial capacity ContinuousInvestment(FixedProfile(0), FixedProfile(20)), # investment mode )] - @test_throws AssertionError build_simple_graph(;inv_data) + @test_throws AssertionError check_graph_inv_node(; inv_data) # Check that we receive an error if we provide a larger `min_add` than `max_add` min_add = FixedProfile(20) - @test_throws AssertionError build_simple_graph(;min_add) + @test_throws AssertionError check_graph_inv_node(; min_add) end # Testing, that the checks for StorageInvData are working # - EMB.check_node_data(n::EMB.Storage, data::InvestmentData, 𝒯, modeltype::AbstractInvestmentModel) @testset "StorageInvData" begin - function build_simple_graph(; + function check_graph_inv_stor(; charge_cap = FixedProfile(0), level_cap = FixedProfile(0), min_add = FixedProfile(0), @@ -571,7 +568,7 @@ EMB.TEST_ENV = true ContinuousInvestment(FixedProfile(0), FixedProfile(20)), # investment mode ) ] - @test_throws AssertionError build_simple_graph(;inv_data) + @test_throws AssertionError check_graph_inv_stor(;inv_data) # Check that we receive an error if we provide two `InvestmentData` inv_data = [ @@ -600,7 +597,7 @@ EMB.TEST_ENV = true ), ), ] - @test_throws AssertionError build_simple_graph(;inv_data) + @test_throws AssertionError check_graph_inv_stor(;inv_data) # Check that we receive an error if the profiles are wrong rprofile = RepresentativeProfile([FixedProfile(4)]) @@ -608,44 +605,44 @@ EMB.TEST_ENV = true oprofile = OperationalProfile(ones(4)) max_add = oprofile - @test_throws AssertionError build_simple_graph(;max_add) + @test_throws AssertionError check_graph_inv_stor(; max_add) max_add = scprofile - @test_throws AssertionError build_simple_graph(;max_add) + @test_throws AssertionError check_graph_inv_stor(; max_add) max_add = rprofile - @test_throws AssertionError build_simple_graph(;max_add) + @test_throws AssertionError check_graph_inv_stor(; max_add) max_add = StrategicProfile([4]) - @test_throws AssertionError build_simple_graph(;max_add) + @test_throws AssertionError check_graph_inv_stor(; max_add) max_add = StrategicProfile([oprofile, oprofile, oprofile, oprofile]) - @test_throws AssertionError build_simple_graph(;max_add) + @test_throws AssertionError check_graph_inv_stor(; max_add) max_add = StrategicProfile([scprofile, scprofile, scprofile, scprofile]) - @test_throws AssertionError build_simple_graph(;max_add) + @test_throws AssertionError check_graph_inv_stor(; max_add) max_add = StrategicProfile([rprofile, rprofile, rprofile, rprofile]) - @test_throws AssertionError build_simple_graph(;max_add) + @test_throws AssertionError check_graph_inv_stor(; max_add) # Check that we receive an error if the capacity is an operational profile charge_cap = OperationalProfile(ones(4)) - @test_throws AssertionError build_simple_graph(;charge_cap) + @test_throws AssertionError check_graph_inv_stor(; charge_cap) level_cap = OperationalProfile(ones(4)) - @test_throws AssertionError build_simple_graph(;level_cap) + @test_throws AssertionError check_graph_inv_stor(; level_cap) # Check that we receive an error if the initial capacity is higher than the # allowed maximum installed charge_cap = FixedProfile(50) - @test_throws AssertionError build_simple_graph(;charge_cap) + @test_throws AssertionError check_graph_inv_stor(; charge_cap) level_cap = FixedProfile(10000) - @test_throws AssertionError build_simple_graph(;level_cap) + @test_throws AssertionError check_graph_inv_stor(; level_cap) # Check that we receive an error if we provide a larger `min_add` than `max_add` min_add = FixedProfile(20) - @test_throws AssertionError build_simple_graph(;min_add) + @test_throws AssertionError check_graph_inv_stor(; min_add) end # Testing, that the checks for Links are working # - EMB.check_link_data(n::Link, data::InvestmentData, 𝒯, modeltype::AbstractInvestmentModel) @testset "SingleInvData" begin - function build_simple_graph(; + function check_graph_inv_link(; cap = FixedProfile(0), min_add = FixedProfile(0), max_add = FixedProfile(10), @@ -703,11 +700,11 @@ EMB.TEST_ENV = true ContinuousInvestment(FixedProfile(0), FixedProfile(20)), # investment mode ), ] - @test_throws AssertionError build_simple_graph(;inv_data) + @test_throws AssertionError check_graph_inv_link(; inv_data) # Check that the correct subtroutine is called max_add = RepresentativeProfile([FixedProfile(4)]) - @test_throws AssertionError build_simple_graph(;max_add) + @test_throws AssertionError check_graph_inv_link(; max_add) end end