From 79f7472a2308a4f2f67c225173d2a6b1e71c5b19 Mon Sep 17 00:00:00 2001 From: Julian Straus Date: Sat, 27 Jun 2026 22:01:22 +0200 Subject: [PATCH 1/8] Rewrote existing profile checks for reusability --- Project.toml | 2 +- src/checks.jl | 116 +++++++++++++++++++++----------------------- test/test_checks.jl | 26 +++++++++- 3 files changed, 79 insertions(+), 65 deletions(-) diff --git a/Project.toml b/Project.toml index f216017..c8c2711 100644 --- a/Project.toml +++ b/Project.toml @@ -18,5 +18,5 @@ EMIExt = "EnergyModelsInvestments" EnergyModelsInvestments = "0.9" JuMP = "1" SparseVariables = "0.7.3" -TimeStruct = "0.9.11" +TimeStruct = "0.9.12" julia = "1.10" diff --git a/src/checks.jl b/src/checks.jl index 3f89ab2..babfc2f 100644 --- a/src/checks.jl +++ b/src/checks.jl @@ -639,6 +639,12 @@ function check_profile( end check_profile(fieldname, value, ts, p_msg) = nothing +function check_not_sub_prof(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) where {TP} + bool_tp = !isa(tp_sub, TP) + @assert_or_log(bool_tp, "`$(TP)`s " * sub_msg) + return bool_tp +end + """ check_strategic_profile(time_profile::TimeProfile, message::String) @@ -652,40 +658,31 @@ strategic indexing. """ function check_strategic_profile(time_profile::TimeProfile, message::String) # Check on the highest level - bool_sp = check_strat_sub_profile(time_profile, message, true) + bool_sp = check_strat_sub_profile(time_profile, message) if isa(time_profile, StrategicProfile) - for l1_profile ∈ time_profile.vals + for tp_l1 ∈ time_profile.vals sub_msg = "in strategic profiles " * message - bool_sp = check_strat_sub_profile(l1_profile, sub_msg, bool_sp) + bool_sp *= check_strat_sub_profile(tp_l1, sub_msg) !bool_sp && break end elseif isa(time_profile, StrategicStochasticProfile) - for sp_array ∈ time_profile.vals, l1_profile ∈ sp_array + for sp_array ∈ time_profile.vals, tp_l1 ∈ sp_array sub_msg = "in strategic stochastic profiles " * message - bool_sp = check_strat_sub_profile(l1_profile, sub_msg, bool_sp) + bool_sp *= check_strat_sub_profile(tp_l1, sub_msg) !bool_sp && break end end return bool_sp end -function check_strat_sub_profile(sub_profile::TimeProfile, sub_msg::String, bool_sp::Bool) - @assert_or_log( - !isa(sub_profile, OperationalProfile), - "Operational profiles " * sub_msg - ) - @assert_or_log(!isa(sub_profile, ScenarioProfile), "Scenario profiles " * sub_msg) - @assert_or_log( - !isa(sub_profile, RepresentativeProfile), - "Representative profiles " * sub_msg - ) +function check_strat_sub_profile(tp_sub::TimeProfile, sub_msg::String) + bool_op = check_not_sub_prof(OperationalProfile, tp_sub, sub_msg) + bool_part = check_not_sub_prof(PartitionProfile, tp_sub, sub_msg) + bool_scp = check_not_sub_prof(ScenarioProfile, tp_sub, sub_msg) + bool_rp = check_not_sub_prof(RepresentativeProfile, tp_sub, sub_msg) - bool_sp *= - !isa(sub_profile, OperationalProfile) && - !isa(sub_profile, ScenarioProfile) && - !isa(sub_profile, RepresentativeProfile) - return bool_sp + return bool_op * bool_part * bool_scp * bool_rp end @@ -705,17 +702,17 @@ representative periods indexing. """ function check_representative_profile(time_profile::TimeProfile, message::String) # Check on the highest level - bool_rp = check_repr_sub_profile(time_profile, message, true) + bool_rp = check_repr_sub_profile(time_profile, message) # Iterate through the strategic profiles, if existing if isa(time_profile, StrategicProfile) - for l1_profile ∈ time_profile.vals + for tp_l1 ∈ time_profile.vals sub_msg_1 = "in strategic profiles " * message - bool_rp = check_repr_sub_profile(l1_profile, sub_msg_1, bool_rp) - if isa(l1_profile, RepresentativeProfile) - for l2_profile ∈ l1_profile.vals + bool_rp *= check_repr_sub_profile(tp_l1, sub_msg_1) + if isa(tp_l1, RepresentativeProfile) + for tp_l2 ∈ tp_l1.vals sub_msg_2 = "in representative profiles " * sub_msg_1 - bool_rp = check_repr_sub_profile(l2_profile, sub_msg_2, bool_rp) + bool_rp *= check_repr_sub_profile(tp_l2, sub_msg_2) end end end @@ -723,22 +720,19 @@ function check_representative_profile(time_profile::TimeProfile, message::String # Iterate through the representative profiles, if existing if isa(time_profile, RepresentativeProfile) - for l1_profile ∈ time_profile.vals + for tp_l1 ∈ time_profile.vals sub_msg = "in representative profiles " * message - bool_rp = check_repr_sub_profile(l1_profile, sub_msg, bool_rp) + bool_rp *= check_repr_sub_profile(tp_l1, sub_msg) end end return bool_rp end -function check_repr_sub_profile(sub_profile::TimeProfile, sub_msg::String, bool_rp::Bool) - @assert_or_log( - !isa(sub_profile, OperationalProfile), - "Operational profiles " * sub_msg - ) - @assert_or_log(!isa(sub_profile, ScenarioProfile), "Scenario profiles " * sub_msg) +function check_repr_sub_profile(tp_sub::TimeProfile, sub_msg::String) + bool_op = check_not_sub_prof(OperationalProfile, tp_sub, sub_msg) + bool_part = check_not_sub_prof(PartitionProfile, tp_sub, sub_msg) + bool_scp = check_not_sub_prof(ScenarioProfile, tp_sub, sub_msg) - bool_rp *= !isa(sub_profile, OperationalProfile) && !isa(sub_profile, ScenarioProfile) - return bool_rp + return bool_op * bool_part * bool_scp end """ @@ -753,28 +747,28 @@ scenario indexing. """ function check_scenario_profile(time_profile::TimeProfile, message::String) # Check on the highest level - bool_scp = check_osc_sub_profile(time_profile, message, true) + bool_scp = check_osc_sub_profile(time_profile, message) # Iterate through the strategic profiles, if existing if isa(time_profile, StrategicProfile) - for l1_profile ∈ time_profile.vals + for tp_l1 ∈ time_profile.vals sub_msg_1 = "in strategic profiles " * message - bool_scp = check_osc_sub_profile(l1_profile, sub_msg_1, bool_scp) - if isa(l1_profile, RepresentativeProfile) + bool_scp *= check_osc_sub_profile(tp_l1, sub_msg_1) + if isa(tp_l1, RepresentativeProfile) sub_msg_2 = "in representative profiles " * sub_msg_1 - for l2_profile ∈ l1_profile.vals - bool_scp = check_osc_sub_profile(l2_profile, sub_msg_2, bool_scp) - if isa(l2_profile, ScenarioProfile) + for tp_l2 ∈ tp_l1.vals + bool_scp *= check_osc_sub_profile(tp_l2, sub_msg_2) + if isa(tp_l2, ScenarioProfile) sub_msg_3 = "in scenario profiles in " * sub_msg_2 - for l3_profile ∈ l2_profile.vals - bool_scp = check_osc_sub_profile(l3_profile, sub_msg_3, bool_scp) + for l3_profile ∈ tp_l2.vals + bool_scp *= check_osc_sub_profile(l3_profile, sub_msg_3) end end end - elseif isa(l1_profile, ScenarioProfile) - for l2_profile ∈ l1_profile.vals + elseif isa(tp_l1, ScenarioProfile) + for tp_l2 ∈ tp_l1.vals sub_msg_2 = "in scenario profiles " * sub_msg_1 - bool_scp = check_osc_sub_profile(l2_profile, sub_msg_2, bool_scp) + bool_scp *= check_osc_sub_profile(tp_l2, sub_msg_2) end end end @@ -782,13 +776,13 @@ function check_scenario_profile(time_profile::TimeProfile, message::String) # Iterate through the representative profiles, if existing if isa(time_profile, RepresentativeProfile) - for l1_profile ∈ time_profile.vals + for tp_l1 ∈ time_profile.vals sub_msg_1 = "in representative profiles " * message - bool_scp = check_osc_sub_profile(l1_profile, sub_msg_1, bool_scp) - if isa(l1_profile, ScenarioProfile) - for l2_profile ∈ l1_profile.vals + bool_scp *= check_osc_sub_profile(tp_l1, sub_msg_1) + if isa(tp_l1, ScenarioProfile) + for tp_l2 ∈ tp_l1.vals sub_msg_2 = "in scenario profiles " * sub_msg_1 - bool_scp = check_osc_sub_profile(l2_profile, sub_msg_2, bool_scp) + bool_scp *= check_osc_sub_profile(tp_l2, sub_msg_2) end end end @@ -796,20 +790,18 @@ function check_scenario_profile(time_profile::TimeProfile, message::String) # Iterate through the scenario profiles, if existing if isa(time_profile, ScenarioProfile) - for l1_profile ∈ time_profile.vals + for tp_l1 ∈ time_profile.vals sub_msg = "in scenario profiles " * message - bool_scp = check_osc_sub_profile(l1_profile, sub_msg, bool_scp) + bool_scp *= check_osc_sub_profile(tp_l1, sub_msg) end end return bool_scp end -function check_osc_sub_profile(sub_profile::TimeProfile, sub_msg::String, bool_scp::Bool) - @assert_or_log( - !isa(sub_profile, OperationalProfile), - "Operational profiles " * sub_msg - ) - bool_scp *= !isa(sub_profile, OperationalProfile) - return bool_scp +function check_osc_sub_profile(tp_sub::TimeProfile, sub_msg::String) + bool_op = check_not_sub_prof(OperationalProfile, tp_sub, sub_msg) + bool_part = check_not_sub_prof(PartitionProfile, tp_sub, sub_msg) + + return bool_op * bool_part end """ diff --git a/test/test_checks.jl b/test/test_checks.jl index b7ef269..3be6788 100644 --- a/test/test_checks.jl +++ b/test/test_checks.jl @@ -363,24 +363,32 @@ end # - EMB.check_profile(fieldname, value::OperationalProfile, ts::OperationalScenarios, sp) oscs = OperationalScenarios(3, day) ts = TwoLevel(2, 1, oscs) + ts_tree = TwoLevelTree(1, [2] ,oscs) for tp ∈ profiles @test_throws AssertionError create_simple_graph(ts, tp) + @test_throws AssertionError create_simple_graph(ts_tree, tp) end # Test that there is an error with wrong `OperationalProfile`s in representative periods # - EMB.check_profile(fieldname, value::OperationalProfile, ts::RepresentativePeriods, sp) - ts = TwoLevel(2, 1, RepresentativePeriods(2, [.5, .5], day)) + rps = RepresentativePeriods(2, [.5, .5], day) + ts = TwoLevel(2, 1, rps) + ts_tree = TwoLevelTree(1, [2], rps) for tp ∈ profiles @test_throws AssertionError create_simple_graph(ts, tp) + @test_throws AssertionError create_simple_graph(ts_tree, tp) end # Test that there is an error with wrong `OperationalProfile`s in operational scenarios # in representative periods # - EMB.check_profile(fieldname, value::OperationalProfile, ts::OperationalScenarios, sp) # - EMB.check_profile(fieldname, value::OperationalProfile, ts::RepresentativePeriods, sp) - ts = TwoLevel(2, 1, RepresentativePeriods(2, [.5, .5], oscs)) + rps = RepresentativePeriods(2, [.5, .5], oscs) + ts = TwoLevel(2, 1, rps) + ts_tree = TwoLevelTree(1, [2], rps) for tp ∈ profiles @test_throws AssertionError create_simple_graph(ts, tp) + @test_throws AssertionError create_simple_graph(ts_tree, tp) end # Test that there is warning when using `ScenarioProfile` without `OperationalScenarios` @@ -457,11 +465,14 @@ end # - EMB.check_strategic_profile(time_profile::TimeProfile, message::String) profiles = [ OperationalProfile([5]), + PartitionProfile([5]), ScenarioProfile([5]), RepresentativeProfile([5]), StrategicProfile([OperationalProfile([5])]), + StrategicProfile([PartitionProfile([5])]), StrategicProfile([ScenarioProfile([5])]), StrategicProfile([RepresentativeProfile([5])]), + StrategicStochasticProfile([[PartitionProfile([5])]]), StrategicStochasticProfile([[OperationalProfile([5])]]), StrategicStochasticProfile([[ScenarioProfile([5])]]), StrategicStochasticProfile([[RepresentativeProfile([5])]]), @@ -474,12 +485,16 @@ end # - EMB.check_representative_profile(time_profile::TimeProfile, message::String) profiles = [ OperationalProfile([5]), + PartitionProfile([5]), ScenarioProfile([5]), StrategicProfile([OperationalProfile([5])]), + StrategicProfile([PartitionProfile([5])]), StrategicProfile([ScenarioProfile([5])]), StrategicProfile([RepresentativeProfile([OperationalProfile([5])])]), + StrategicProfile([RepresentativeProfile([PartitionProfile([5])])]), StrategicProfile([RepresentativeProfile([ScenarioProfile([5])])]), RepresentativeProfile([OperationalProfile([5])]), + RepresentativeProfile([PartitionProfile([5])]), RepresentativeProfile([ScenarioProfile([5])]), ] for tp ∈ profiles @@ -490,12 +505,19 @@ end # - EMB.check_scenario_profile(time_profile::TimeProfile, message::String) profiles = [ OperationalProfile([5]), + PartitionProfile([5]), StrategicProfile([OperationalProfile([5])]), + StrategicProfile([PartitionProfile([5])]), StrategicProfile([RepresentativeProfile([OperationalProfile([5])])]), + StrategicProfile([RepresentativeProfile([PartitionProfile([5])])]), StrategicProfile([RepresentativeProfile([ScenarioProfile([OperationalProfile([5])])])]), + StrategicProfile([RepresentativeProfile([ScenarioProfile([PartitionProfile([5])])])]), RepresentativeProfile([OperationalProfile([5])]), + RepresentativeProfile([PartitionProfile([5])]), RepresentativeProfile([ScenarioProfile([OperationalProfile([5])])]), + RepresentativeProfile([ScenarioProfile([PartitionProfile([5])])]), ScenarioProfile([OperationalProfile([5])]), + ScenarioProfile([PartitionProfile([5])]), ] for tp ∈ profiles @test_throws AssertionError EMB.check_scenario_profile(tp, "") From aab723950b286f6f0ed6cadeef248ab9e95cd753 Mon Sep 17 00:00:00 2001 From: Julian Straus Date: Sat, 27 Jun 2026 22:46:02 +0200 Subject: [PATCH 2/8] Reworked strategic profile checks --- src/checks.jl | 79 ++++++++++++++++++++++++++++----------------- test/test_checks.jl | 11 +++++++ 2 files changed, 61 insertions(+), 29 deletions(-) diff --git a/src/checks.jl b/src/checks.jl index babfc2f..545f45e 100644 --- a/src/checks.jl +++ b/src/checks.jl @@ -645,6 +645,43 @@ function check_not_sub_prof(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) wh return bool_tp end +function check_not_profiles(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) where {TP<:Union{StrategicProfile, StrategicStochasticProfile}} + bool_op = check_not_sub_prof(OperationalProfile, tp_sub, sub_msg) + bool_part = check_not_sub_prof(PartitionProfile, tp_sub, sub_msg) + bool_scp = check_not_sub_prof(ScenarioProfile, tp_sub, sub_msg) + bool_rp = check_not_sub_prof(RepresentativeProfile, tp_sub, sub_msg) + + return bool_op * bool_part * bool_scp * bool_rp +end + +function check_sub_profs( + ::Type{TP}, + time_profile::StrategicProfile, + msg::String, +) where {TP<:StrategicProfile} + bool_val = true + for l1_profile ∈ time_profile.vals + sub_msg = "in strategic profiles " * msg + bool_val *= check_not_profiles(TP, l1_profile, sub_msg) + !bool_val && break + end + return bool_val +end + +function check_sub_profs( + ::Type{TP}, + time_profile::StrategicStochasticProfile, + msg::String, +) where {TP<:StrategicStochasticProfile} + bool_val = true + for sp_array ∈ time_profile.vals, l1_profile ∈ sp_array + sub_msg = "in strategic stochastic profiles " * msg + bool_val = check_not_profiles(TP, l1_profile, sub_msg) + !bool_val && break + end + return bool_val +end + """ check_strategic_profile(time_profile::TimeProfile, message::String) @@ -653,38 +690,21 @@ strategic indexing. ## Checks - `TimeProfile`s accessed in `StrategicPeriod`s cannot include `OperationalProfile`, - `ScenarioProfile`, or `RepresentativeProfile` as this is not allowed through indexing - on the `TimeProfile`. + `PartitionProfile`, `ScenarioProfile`, or `RepresentativeProfile` as this is not allowed + through indexing on the `TimeProfile`. """ function check_strategic_profile(time_profile::TimeProfile, message::String) - # Check on the highest level - bool_sp = check_strat_sub_profile(time_profile, message) - if isa(time_profile, StrategicProfile) - for tp_l1 ∈ time_profile.vals - sub_msg = "in strategic profiles " * message - bool_sp *= check_strat_sub_profile(tp_l1, sub_msg) - !bool_sp && break - end - elseif isa(time_profile, StrategicStochasticProfile) - for sp_array ∈ time_profile.vals, tp_l1 ∈ sp_array - sub_msg = "in strategic stochastic profiles " * message - bool_sp *= check_strat_sub_profile(tp_l1, sub_msg) - !bool_sp && break - end + if isa(time_profile, Union{StrategicProfile, StrategicStochasticProfile}) + # Iterate through the strategic or strategic stochstic profiles, if existing + bool_sp = check_sub_profs(typeof(time_profile), time_profile, message) + else + # Check the profiles + bool_sp = check_not_profiles(StrategicProfile, time_profile, message) end return bool_sp end -function check_strat_sub_profile(tp_sub::TimeProfile, sub_msg::String) - bool_op = check_not_sub_prof(OperationalProfile, tp_sub, sub_msg) - bool_part = check_not_sub_prof(PartitionProfile, tp_sub, sub_msg) - bool_scp = check_not_sub_prof(ScenarioProfile, tp_sub, sub_msg) - bool_rp = check_not_sub_prof(RepresentativeProfile, tp_sub, sub_msg) - - return bool_op * bool_part * bool_scp * bool_rp -end - """ check_representative_profile(time_profile::TimeProfile, message::String) @@ -697,8 +717,9 @@ representative periods indexing. - `message` - A message that should be printed after the type of profile. ## Checks -- `TimeProfile`s accessed in `RepresentativePeriod`s cannot include `OperationalProfile` - or `ScenarioProfile` as this is not allowed through indexing on the `TimeProfile`. +- `TimeProfile`s accessed in `RepresentativePeriod`s cannot include `OperationalProfile`, + `PartitionProfile` or `ScenarioProfile` as this is not allowed through indexing on the + `TimeProfile`. """ function check_representative_profile(time_profile::TimeProfile, message::String) # Check on the highest level @@ -742,8 +763,8 @@ Function for checking that an individual `TimeProfile` does not include the wron scenario indexing. ## Checks -- `TimeProfile`s accessed in `OperationalScenario`s cannot include `OperationalProfile` as - this is not allowed through indexing on the `TimeProfile`. +- `TimeProfile`s accessed in `OperationalScenario`s cannot include `OperationalProfile` or + `PartitionProfile` as this is not allowed through indexing on the `TimeProfile`. """ function check_scenario_profile(time_profile::TimeProfile, message::String) # Check on the highest level diff --git a/test/test_checks.jl b/test/test_checks.jl index 3be6788..6ca93cd 100644 --- a/test/test_checks.jl +++ b/test/test_checks.jl @@ -481,6 +481,17 @@ end @test_throws AssertionError EMB.check_strategic_profile(tp, "") end + # Check that valid profiles pass check_strategic_profile without error + valid_profiles = [ + FixedProfile(5), + StrategicProfile([FixedProfile(5)]), + StrategicStochasticProfile([[FixedProfile(5)]]), + ] + for tp ∈ valid_profiles + @test EMB.check_strategic_profile(tp, "") + end + + # Check that wrong profiles for representative indexable variables are identified # - EMB.check_representative_profile(time_profile::TimeProfile, message::String) profiles = [ From 903ee3e66e0407cb5cc7c00d123f2144816c9349 Mon Sep 17 00:00:00 2001 From: Julian Straus Date: Sat, 27 Jun 2026 23:32:21 +0200 Subject: [PATCH 3/8] Reworked representative profile checks --- src/checks.jl | 97 +++++++++++++++++++++++++++++++-------------- test/test_checks.jl | 16 ++++++++ 2 files changed, 84 insertions(+), 29 deletions(-) diff --git a/src/checks.jl b/src/checks.jl index 545f45e..69fe34d 100644 --- a/src/checks.jl +++ b/src/checks.jl @@ -654,6 +654,14 @@ function check_not_profiles(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) wh return bool_op * bool_part * bool_scp * bool_rp end +function check_not_profiles(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) where {TP<:RepresentativeProfile} + bool_op = check_not_sub_prof(OperationalProfile, tp_sub, sub_msg) + bool_part = check_not_sub_prof(PartitionProfile, tp_sub, sub_msg) + bool_scp = check_not_sub_prof(ScenarioProfile, tp_sub, sub_msg) + + return bool_op * bool_part * bool_scp +end + function check_sub_profs( ::Type{TP}, time_profile::StrategicProfile, @@ -667,6 +675,20 @@ function check_sub_profs( end return bool_val end +function check_sub_profs( + ::Type{TP}, + time_profile::StrategicProfile, + msg::String, +) where {TP<:TimeProfile} + bool_val = true + for l1_profile ∈ time_profile.vals + sub_msg = "in strategic profiles " * msg + bool_val *= check_not_profiles(TP, l1_profile, sub_msg) + bool_val *= check_sub_profs(TP, l1_profile, msg) + !bool_val && break + end + return bool_val +end function check_sub_profs( ::Type{TP}, @@ -681,6 +703,42 @@ function check_sub_profs( end return bool_val end +function check_sub_profs( + ::Type{TP}, + time_profile::StrategicStochasticProfile, + msg::String, +) where {TP<:TimeProfile} + bool_val = true + for sp_array ∈ time_profile.vals, l1_profile ∈ sp_array + sub_msg = "in strategic stochastic profiles " * msg + bool_val = check_not_profiles(TP, l1_profile, sub_msg) + bool_val *= check_sub_profs(TP, l1_profile, msg) + !bool_val && break + end + return bool_val +end + +function check_sub_profs( + ::Type{TP}, + time_profile::RepresentativeProfile, + msg::String, +) where {TP<:RepresentativeProfile} + bool_val = true + for l1_profile ∈ time_profile.vals + sub_msg = "in representative profiles " * msg + bool_val *= check_not_profiles(TP, l1_profile, sub_msg) + !bool_val && break + end + return bool_val +end + +function check_sub_profs( + ::Type{TP}, + time_profile::FixedProfile, + msg::String, +) where {TP<:TimeProfile} + return true +end """ check_strategic_profile(time_profile::TimeProfile, message::String) @@ -722,39 +780,20 @@ representative periods indexing. `TimeProfile`. """ function check_representative_profile(time_profile::TimeProfile, message::String) - # Check on the highest level - bool_rp = check_repr_sub_profile(time_profile, message) - - # Iterate through the strategic profiles, if existing - if isa(time_profile, StrategicProfile) - for tp_l1 ∈ time_profile.vals - sub_msg_1 = "in strategic profiles " * message - bool_rp *= check_repr_sub_profile(tp_l1, sub_msg_1) - if isa(tp_l1, RepresentativeProfile) - for tp_l2 ∈ tp_l1.vals - sub_msg_2 = "in representative profiles " * sub_msg_1 - bool_rp *= check_repr_sub_profile(tp_l2, sub_msg_2) - end - end - end - end - # Iterate through the representative profiles, if existing - if isa(time_profile, RepresentativeProfile) - for tp_l1 ∈ time_profile.vals - sub_msg = "in representative profiles " * message - bool_rp *= check_repr_sub_profile(tp_l1, sub_msg) - end + if isa( + time_profile, + Union{StrategicProfile, StrategicStochasticProfile, RepresentativeProfile} + ) + # Iterate through the strategic, strategic stochstic, or representative profiles, + # if existing + bool_rp = check_sub_profs(RepresentativeProfile, time_profile, message) + else + # Check the profiles + bool_rp = check_not_profiles(RepresentativeProfile, time_profile, message) end return bool_rp end -function check_repr_sub_profile(tp_sub::TimeProfile, sub_msg::String) - bool_op = check_not_sub_prof(OperationalProfile, tp_sub, sub_msg) - bool_part = check_not_sub_prof(PartitionProfile, tp_sub, sub_msg) - bool_scp = check_not_sub_prof(ScenarioProfile, tp_sub, sub_msg) - - return bool_op * bool_part * bool_scp -end """ check_scenario_profile(time_profile::TimeProfile, message::String) diff --git a/test/test_checks.jl b/test/test_checks.jl index 6ca93cd..7780d5c 100644 --- a/test/test_checks.jl +++ b/test/test_checks.jl @@ -501,9 +501,11 @@ end StrategicProfile([OperationalProfile([5])]), StrategicProfile([PartitionProfile([5])]), StrategicProfile([ScenarioProfile([5])]), + StrategicStochasticProfile([[OperationalProfile([5])]]), StrategicProfile([RepresentativeProfile([OperationalProfile([5])])]), StrategicProfile([RepresentativeProfile([PartitionProfile([5])])]), StrategicProfile([RepresentativeProfile([ScenarioProfile([5])])]), + StrategicStochasticProfile([[RepresentativeProfile([OperationalProfile([5])])]]), RepresentativeProfile([OperationalProfile([5])]), RepresentativeProfile([PartitionProfile([5])]), RepresentativeProfile([ScenarioProfile([5])]), @@ -512,6 +514,20 @@ end @test_throws AssertionError EMB.check_representative_profile(tp, "") end + # Check that valid profiles pass check_representative_profile without error + valid_profiles = [ + FixedProfile(5), + RepresentativeProfile([5]), + StrategicProfile([FixedProfile(5)]), + StrategicProfile([RepresentativeProfile([5])]), + StrategicStochasticProfile([[FixedProfile(5)]]), + StrategicStochasticProfile([[RepresentativeProfile([5])]]), + ] + for tp ∈ valid_profiles + @test EMB.check_representative_profile(tp, "") + end + + # Check that wrong profiles for scenario indexable variables are identified # - EMB.check_scenario_profile(time_profile::TimeProfile, message::String) profiles = [ From 033b38171f1a0b92f4822c3b7ef7998017964e09 Mon Sep 17 00:00:00 2001 From: Julian Straus Date: Sat, 27 Jun 2026 23:38:16 +0200 Subject: [PATCH 4/8] Reworked scenario profile checks --- src/checks.jl | 97 ++++++++++++++++++++------------------------- test/test_checks.jl | 2 + 2 files changed, 46 insertions(+), 53 deletions(-) diff --git a/src/checks.jl b/src/checks.jl index 69fe34d..b1d4745 100644 --- a/src/checks.jl +++ b/src/checks.jl @@ -662,6 +662,13 @@ function check_not_profiles(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) wh return bool_op * bool_part * bool_scp end +function check_not_profiles(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) where {TP<:ScenarioProfile} + bool_op = check_not_sub_prof(OperationalProfile, tp_sub, sub_msg) + bool_part = check_not_sub_prof(PartitionProfile, tp_sub, sub_msg) + + return bool_op * bool_part +end + function check_sub_profs( ::Type{TP}, time_profile::StrategicProfile, @@ -731,6 +738,34 @@ function check_sub_profs( end return bool_val end +function check_sub_profs( + ::Type{TP}, + time_profile::RepresentativeProfile, + msg::String, +) where {TP<:TimeProfile} + bool_val = true + for l1_profile ∈ time_profile.vals + sub_msg = "in representative profiles " * msg + bool_val *= check_not_profiles(TP, l1_profile, sub_msg) + bool_val *= check_sub_profs(TP, l1_profile, msg) + !bool_val && break + end + return bool_val +end + +function check_sub_profs( + ::Type{TP}, + time_profile::ScenarioProfile, + msg::String, +) where {TP<:ScenarioProfile} + bool_val = true + for l1_profile ∈ time_profile.vals + sub_msg = "in scenario profiles " * msg + bool_val *= check_not_profiles(TP, l1_profile, sub_msg) + !bool_val && break + end + return bool_val +end function check_sub_profs( ::Type{TP}, @@ -806,63 +841,19 @@ scenario indexing. `PartitionProfile` as this is not allowed through indexing on the `TimeProfile`. """ function check_scenario_profile(time_profile::TimeProfile, message::String) - # Check on the highest level - bool_scp = check_osc_sub_profile(time_profile, message) - - # Iterate through the strategic profiles, if existing - if isa(time_profile, StrategicProfile) - for tp_l1 ∈ time_profile.vals - sub_msg_1 = "in strategic profiles " * message - bool_scp *= check_osc_sub_profile(tp_l1, sub_msg_1) - if isa(tp_l1, RepresentativeProfile) - sub_msg_2 = "in representative profiles " * sub_msg_1 - for tp_l2 ∈ tp_l1.vals - bool_scp *= check_osc_sub_profile(tp_l2, sub_msg_2) - if isa(tp_l2, ScenarioProfile) - sub_msg_3 = "in scenario profiles in " * sub_msg_2 - for l3_profile ∈ tp_l2.vals - bool_scp *= check_osc_sub_profile(l3_profile, sub_msg_3) - end - end - end - elseif isa(tp_l1, ScenarioProfile) - for tp_l2 ∈ tp_l1.vals - sub_msg_2 = "in scenario profiles " * sub_msg_1 - bool_scp *= check_osc_sub_profile(tp_l2, sub_msg_2) - end - end - end - end - - # Iterate through the representative profiles, if existing - if isa(time_profile, RepresentativeProfile) - for tp_l1 ∈ time_profile.vals - sub_msg_1 = "in representative profiles " * message - bool_scp *= check_osc_sub_profile(tp_l1, sub_msg_1) - if isa(tp_l1, ScenarioProfile) - for tp_l2 ∈ tp_l1.vals - sub_msg_2 = "in scenario profiles " * sub_msg_1 - bool_scp *= check_osc_sub_profile(tp_l2, sub_msg_2) - end - end - end + # Check the sub structures + if isa( + time_profile, + Union{StrategicProfile, StrategicStochasticProfile, RepresentativeProfile, ScenarioProfile} + ) + bool_scp = check_sub_profs(ScenarioProfile, time_profile, message) + else + # Check the profiles + bool_scp = check_not_profiles(ScenarioProfile, time_profile, message) end - # Iterate through the scenario profiles, if existing - if isa(time_profile, ScenarioProfile) - for tp_l1 ∈ time_profile.vals - sub_msg = "in scenario profiles " * message - bool_scp *= check_osc_sub_profile(tp_l1, sub_msg) - end - end return bool_scp end -function check_osc_sub_profile(tp_sub::TimeProfile, sub_msg::String) - bool_op = check_not_sub_prof(OperationalProfile, tp_sub, sub_msg) - bool_part = check_not_sub_prof(PartitionProfile, tp_sub, sub_msg) - - return bool_op * bool_part -end """ check_node(n::Node, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool) diff --git a/test/test_checks.jl b/test/test_checks.jl index 7780d5c..d361611 100644 --- a/test/test_checks.jl +++ b/test/test_checks.jl @@ -535,8 +535,10 @@ end PartitionProfile([5]), StrategicProfile([OperationalProfile([5])]), StrategicProfile([PartitionProfile([5])]), + StrategicStochasticProfile([[OperationalProfile([5])]]), StrategicProfile([RepresentativeProfile([OperationalProfile([5])])]), StrategicProfile([RepresentativeProfile([PartitionProfile([5])])]), + StrategicStochasticProfile([[RepresentativeProfile([OperationalProfile([5])])]]), StrategicProfile([RepresentativeProfile([ScenarioProfile([OperationalProfile([5])])])]), StrategicProfile([RepresentativeProfile([ScenarioProfile([PartitionProfile([5])])])]), RepresentativeProfile([OperationalProfile([5])]), From e942594173d20442b1accad152bd0197468d8fd9 Mon Sep 17 00:00:00 2001 From: Julian Straus Date: Sat, 27 Jun 2026 23:52:29 +0200 Subject: [PATCH 5/8] Added partition profile checks --- docs/src/library/internals/functions.md | 1 + src/checks.jl | 61 +++++++++++++++++++++++++ test/test_checks.jl | 35 ++++++++++++++ 3 files changed, 97 insertions(+) diff --git a/docs/src/library/internals/functions.md b/docs/src/library/internals/functions.md index 3c9d188..53bda2d 100644 --- a/docs/src/library/internals/functions.md +++ b/docs/src/library/internals/functions.md @@ -67,6 +67,7 @@ check_profile check_strategic_profile check_representative_profile check_scenario_profile +check_partition_profile compile_logs ``` diff --git a/src/checks.jl b/src/checks.jl index b1d4745..1ffdf97 100644 --- a/src/checks.jl +++ b/src/checks.jl @@ -669,6 +669,14 @@ function check_not_profiles(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) wh return bool_op * bool_part end +function check_not_profiles(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) where {TP<:PartitionProfile} + bool_op = check_not_sub_prof(OperationalProfile, tp_sub, sub_msg) + return bool_op +end +function check_not_profiles(::Type{TP}, tp_sub::Number, sub_msg::String) where {TP<:PartitionProfile} + return true +end + function check_sub_profs( ::Type{TP}, time_profile::StrategicProfile, @@ -766,6 +774,34 @@ function check_sub_profs( end return bool_val end +function check_sub_profs( + ::Type{TP}, + time_profile::ScenarioProfile, + msg::String, +) where {TP<:TimeProfile} + bool_val = true + for l1_profile ∈ time_profile.vals + sub_msg = "in scenario profiles " * msg + bool_val *= check_not_profiles(TP, l1_profile, sub_msg) + bool_val *= check_sub_profs(TP, l1_profile, sub_msg) + !bool_val && break + end + return bool_val +end + +function check_sub_profs( + ::Type{TP}, + time_profile::PartitionProfile, + msg::String, +) where {TP<:PartitionProfile} + bool_val = true + for l1_profile ∈ time_profile.vals + sub_msg = "in partition profiles " * msg + bool_val *= check_not_profiles(TP, l1_profile, sub_msg) + !bool_val && break + end + return bool_val +end function check_sub_profs( ::Type{TP}, @@ -855,6 +891,31 @@ function check_scenario_profile(time_profile::TimeProfile, message::String) return bool_scp end +""" + check_partition_profile(time_profile::TimeProfile, message::String) + +Function for checking that an individual `TimeProfile` does not include the wrong type for +partition indexing. + +## Checks +- `TimeProfile`s accessed in `PeriodPartion`s cannot include `OperationalProfile` as this is + not allowed through indexing on the `TimeProfile`. +""" +function check_partition_profile(time_profile::TimeProfile, message::String) + # Check the sub structures + if isa( + time_profile, + Union{StrategicProfile, StrategicStochasticProfile, RepresentativeProfile, ScenarioProfile, PartitionProfile} + ) + bool_part = check_sub_profs(PartitionProfile, time_profile, message) + else + # Check the profiles + bool_part = check_not_profiles(ScenarioProfile, time_profile, message) + end + + return bool_part +end + """ check_node(n::Node, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool) diff --git a/test/test_checks.jl b/test/test_checks.jl index d361611..bc059ff 100644 --- a/test/test_checks.jl +++ b/test/test_checks.jl @@ -563,6 +563,41 @@ end @test EMB.check_scenario_profile(tp, "") end + # Check that wrong profiles for partition indexable variables are identified + # - EMB.check_partition_profile(time_profile::TimeProfile, message::String) + profiles = [ + OperationalProfile([5]), + StrategicProfile([OperationalProfile([5])]), + StrategicStochasticProfile([[OperationalProfile([5])]]), + StrategicProfile([RepresentativeProfile([OperationalProfile([5])])]), + StrategicStochasticProfile([[RepresentativeProfile([OperationalProfile([5])])]]), + StrategicProfile([RepresentativeProfile([ScenarioProfile([OperationalProfile([5])])])]), + RepresentativeProfile([OperationalProfile([5])]), + RepresentativeProfile([ScenarioProfile([OperationalProfile([5])])]), + ScenarioProfile([OperationalProfile([5])]), + ScenarioProfile([PartitionProfile([OperationalProfile([5])])]), + PartitionProfile([OperationalProfile([5])]), + + ] + for tp ∈ profiles + @test_throws AssertionError EMB.check_partition_profile(tp, "") + end + + # Check that valid profiles pass check_partition_profile without error + valid_profiles = [ + FixedProfile(5), + PartitionProfile([5]), + StrategicProfile([FixedProfile(5)]), + StrategicProfile([PartitionProfile([5])]), + StrategicProfile([RepresentativeProfile([PartitionProfile([5])])]), + StrategicStochasticProfile([[PartitionProfile([5])]]), + StrategicStochasticProfile([[PartitionProfile([5])]]), + RepresentativeProfile([ScenarioProfile([FixedProfile(5)])]), + ] + for tp ∈ valid_profiles + @test EMB.check_partition_profile(tp, "") + end + # Reactivate logging EMB.ASSERTS_AS_LOG = true end From e4c3dee85b59124d0ad91d9ea058e4c6d6164fab Mon Sep 17 00:00:00 2001 From: Julian Straus Date: Sun, 28 Jun 2026 00:01:39 +0200 Subject: [PATCH 6/8] Added docstring for main function --- docs/src/library/internals/functions.md | 2 ++ src/checks.jl | 25 ++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/docs/src/library/internals/functions.md b/docs/src/library/internals/functions.md index 53bda2d..32d63b4 100644 --- a/docs/src/library/internals/functions.md +++ b/docs/src/library/internals/functions.md @@ -68,6 +68,8 @@ check_strategic_profile check_representative_profile check_scenario_profile check_partition_profile +check_sub_profs +check_not_profiles compile_logs ``` diff --git a/src/checks.jl b/src/checks.jl index 1ffdf97..ffccbbe 100644 --- a/src/checks.jl +++ b/src/checks.jl @@ -645,6 +645,17 @@ function check_not_sub_prof(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) wh return bool_tp end +""" + check_not_profiles(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) where {T<:TimeProfile} + +Check that the sub time profile `tp_sub` is applicable for indexing with `TimeStructurePeriod` +or `PeriodPartition` as declared by the time profile type `TP`. The check is not performed +on potential subprofiles. This can be achieved through the function [`check_sub_profs`](@ref) + +!!! danger "Usage of this function" + User should never use this function directly. It should only be included within the core + structure in `EnergyModelsBase`. +""" function check_not_profiles(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) where {TP<:Union{StrategicProfile, StrategicStochasticProfile}} bool_op = check_not_sub_prof(OperationalProfile, tp_sub, sub_msg) bool_part = check_not_sub_prof(PartitionProfile, tp_sub, sub_msg) @@ -653,7 +664,6 @@ function check_not_profiles(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) wh return bool_op * bool_part * bool_scp * bool_rp end - function check_not_profiles(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) where {TP<:RepresentativeProfile} bool_op = check_not_sub_prof(OperationalProfile, tp_sub, sub_msg) bool_part = check_not_sub_prof(PartitionProfile, tp_sub, sub_msg) @@ -661,14 +671,12 @@ function check_not_profiles(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) wh return bool_op * bool_part * bool_scp end - function check_not_profiles(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) where {TP<:ScenarioProfile} bool_op = check_not_sub_prof(OperationalProfile, tp_sub, sub_msg) bool_part = check_not_sub_prof(PartitionProfile, tp_sub, sub_msg) return bool_op * bool_part end - function check_not_profiles(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) where {TP<:PartitionProfile} bool_op = check_not_sub_prof(OperationalProfile, tp_sub, sub_msg) return bool_op @@ -677,6 +685,17 @@ function check_not_profiles(::Type{TP}, tp_sub::Number, sub_msg::String) where { return true end +""" + check_sub_profs(::Type{TP}, time_profile::TimeProfile, msg::String) where {TP<:TimeProfile} + +Check that the provided `time_profile` allows indexing as specified through the provided +`TP` profile type. Each sub profile is checked if the `time_profile` includes sub profiles +different than `FixedProfile`. The check is based on the provided profile type `TP`. + +!!! danger "Usage of these functions" + User should never use these functions directly. They should only be included within the + core structure in `EnergyModelsBase`. +""" function check_sub_profs( ::Type{TP}, time_profile::StrategicProfile, From c489c86a60c615bd1083098972f4c82f261ad43b Mon Sep 17 00:00:00 2001 From: Julian Straus Date: Sun, 28 Jun 2026 11:36:02 +0200 Subject: [PATCH 7/8] Simplified function work flow --- docs/src/library/internals/functions.md | 3 +- src/checks.jl | 189 ++++++++---------------- 2 files changed, 61 insertions(+), 131 deletions(-) diff --git a/docs/src/library/internals/functions.md b/docs/src/library/internals/functions.md index 32d63b4..94c2938 100644 --- a/docs/src/library/internals/functions.md +++ b/docs/src/library/internals/functions.md @@ -69,7 +69,8 @@ check_representative_profile check_scenario_profile check_partition_profile check_sub_profs -check_not_profiles +check_sub_profile +check_not_profile compile_logs ``` diff --git a/src/checks.jl b/src/checks.jl index ffccbbe..e0137a7 100644 --- a/src/checks.jl +++ b/src/checks.jl @@ -639,16 +639,21 @@ function check_profile( end check_profile(fieldname, value, ts, p_msg) = nothing -function check_not_sub_prof(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) where {TP} - bool_tp = !isa(tp_sub, TP) +""" + check_not_profile(::Type{TP}, sub_prof::TimeProfile, sub_msg::String) where {TP<:TimeProfile} + +Check that the profile `sub_prof` is not of type `TP`. +""" +function check_not_profile(::Type{TP}, sub_prof::TimeProfile, sub_msg::String) where {TP<:TimeProfile} + bool_tp = !isa(sub_prof, TP) @assert_or_log(bool_tp, "`$(TP)`s " * sub_msg) return bool_tp end """ - check_not_profiles(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) where {T<:TimeProfile} + check_sub_profile(::Type{TP}, sub_prof::TimeProfile, sub_msg::String) where {T<:TimeProfile} -Check that the sub time profile `tp_sub` is applicable for indexing with `TimeStructurePeriod` +Check that the time profile `sub_prof` is applicable for indexing with `TimeStructurePeriod` or `PeriodPartition` as declared by the time profile type `TP`. The check is not performed on potential subprofiles. This can be achieved through the function [`check_sub_profs`](@ref) @@ -656,177 +661,101 @@ on potential subprofiles. This can be achieved through the function [`check_sub_ User should never use this function directly. It should only be included within the core structure in `EnergyModelsBase`. """ -function check_not_profiles(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) where {TP<:Union{StrategicProfile, StrategicStochasticProfile}} - bool_op = check_not_sub_prof(OperationalProfile, tp_sub, sub_msg) - bool_part = check_not_sub_prof(PartitionProfile, tp_sub, sub_msg) - bool_scp = check_not_sub_prof(ScenarioProfile, tp_sub, sub_msg) - bool_rp = check_not_sub_prof(RepresentativeProfile, tp_sub, sub_msg) +function check_sub_profile(::Type{TP}, sub_prof::TimeProfile, sub_msg::String) where {TP<:Union{StrategicProfile, StrategicStochasticProfile}} + bool_op = check_not_profile(OperationalProfile, sub_prof, sub_msg) + bool_part = check_not_profile(PartitionProfile, sub_prof, sub_msg) + bool_scp = check_not_profile(ScenarioProfile, sub_prof, sub_msg) + bool_rp = check_not_profile(RepresentativeProfile, sub_prof, sub_msg) return bool_op * bool_part * bool_scp * bool_rp end -function check_not_profiles(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) where {TP<:RepresentativeProfile} - bool_op = check_not_sub_prof(OperationalProfile, tp_sub, sub_msg) - bool_part = check_not_sub_prof(PartitionProfile, tp_sub, sub_msg) - bool_scp = check_not_sub_prof(ScenarioProfile, tp_sub, sub_msg) +function check_sub_profile(::Type{TP}, sub_prof::TimeProfile, sub_msg::String) where {TP<:RepresentativeProfile} + bool_op = check_not_profile(OperationalProfile, sub_prof, sub_msg) + bool_part = check_not_profile(PartitionProfile, sub_prof, sub_msg) + bool_scp = check_not_profile(ScenarioProfile, sub_prof, sub_msg) return bool_op * bool_part * bool_scp end -function check_not_profiles(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) where {TP<:ScenarioProfile} - bool_op = check_not_sub_prof(OperationalProfile, tp_sub, sub_msg) - bool_part = check_not_sub_prof(PartitionProfile, tp_sub, sub_msg) +function check_sub_profile(::Type{TP}, sub_prof::TimeProfile, sub_msg::String) where {TP<:ScenarioProfile} + bool_op = check_not_profile(OperationalProfile, sub_prof, sub_msg) + bool_part = check_not_profile(PartitionProfile, sub_prof, sub_msg) return bool_op * bool_part end -function check_not_profiles(::Type{TP}, tp_sub::TimeProfile, sub_msg::String) where {TP<:PartitionProfile} - bool_op = check_not_sub_prof(OperationalProfile, tp_sub, sub_msg) +function check_sub_profile(::Type{TP}, sub_prof::TimeProfile, sub_msg::String) where {TP<:PartitionProfile} + bool_op = check_not_profile(OperationalProfile, sub_prof, sub_msg) return bool_op end -function check_not_profiles(::Type{TP}, tp_sub::Number, sub_msg::String) where {TP<:PartitionProfile} +function check_sub_profile(::Type{TP}, sub_prof::Number, sub_msg::String) where {TP<:PartitionProfile} return true end """ - check_sub_profs(::Type{TP}, time_profile::TimeProfile, msg::String) where {TP<:TimeProfile} + check_sub_profs(check_type::Type{TP_C}, prof::TP, msg::String) where {TP_C<:TimeProfile, TP<:TimeProfile} -Check that the provided `time_profile` allows indexing as specified through the provided -`TP` profile type. Each sub profile is checked if the `time_profile` includes sub profiles -different than `FixedProfile`. The check is based on the provided profile type `TP`. +Check that the provided `prof` allows indexing as specified through the provided +`TP_C` profile type. Each sub profile is checked if the `prof` includes sub profiles +different than `FixedProfile`. The check is based on the provided profile type `TP_C`. !!! danger "Usage of these functions" User should never use these functions directly. They should only be included within the core structure in `EnergyModelsBase`. """ -function check_sub_profs( - ::Type{TP}, - time_profile::StrategicProfile, - msg::String, -) where {TP<:StrategicProfile} +function check_sub_profs(::Type{TP_C}, prof::TP_C, msg::String) where {TP_C<:TimeProfile} bool_val = true - for l1_profile ∈ time_profile.vals - sub_msg = "in strategic profiles " * msg - bool_val *= check_not_profiles(TP, l1_profile, sub_msg) + sub_msg = "in `$(TP_C)`s " * msg + for sub_prof ∈ prof.vals + bool_val *= check_sub_profile(TP_C, sub_prof, sub_msg) !bool_val && break end return bool_val end function check_sub_profs( - ::Type{TP}, - time_profile::StrategicProfile, + ::Type{TP_C}, + prof::TP, msg::String, -) where {TP<:TimeProfile} +) where {TP_C<:TimeProfile, TP<:TimeProfile} bool_val = true - for l1_profile ∈ time_profile.vals - sub_msg = "in strategic profiles " * msg - bool_val *= check_not_profiles(TP, l1_profile, sub_msg) - bool_val *= check_sub_profs(TP, l1_profile, msg) + sub_msg = "in `$(TP.name.name)`s " * msg + for sub_prof ∈ prof.vals + bool_val *= check_sub_profile(TP_C, sub_prof, sub_msg) + bool_val *= check_sub_profs(TP_C, sub_prof, sub_msg) !bool_val && break end return bool_val end - function check_sub_profs( - ::Type{TP}, - time_profile::StrategicStochasticProfile, + ::Type{TP_C}, + prof::TP_C, msg::String, -) where {TP<:StrategicStochasticProfile} +) where {TP_C<:StrategicStochasticProfile} bool_val = true - for sp_array ∈ time_profile.vals, l1_profile ∈ sp_array - sub_msg = "in strategic stochastic profiles " * msg - bool_val = check_not_profiles(TP, l1_profile, sub_msg) + sub_msg = "in `StrategicStochasticProfile`s " * msg + for sp_array ∈ prof.vals, sub_prof ∈ sp_array + bool_val = check_sub_profile(TP_C, sub_prof, sub_msg) !bool_val && break end return bool_val end function check_sub_profs( - ::Type{TP}, - time_profile::StrategicStochasticProfile, + ::Type{TP_C}, + prof::StrategicStochasticProfile, msg::String, -) where {TP<:TimeProfile} +) where {TP_C<:TimeProfile} bool_val = true - for sp_array ∈ time_profile.vals, l1_profile ∈ sp_array - sub_msg = "in strategic stochastic profiles " * msg - bool_val = check_not_profiles(TP, l1_profile, sub_msg) - bool_val *= check_sub_profs(TP, l1_profile, msg) + sub_msg = "in `StrategicStochasticProfile`s " * msg + for sp_array ∈ prof.vals, sub_prof ∈ sp_array + bool_val = check_sub_profile(TP_C, sub_prof, sub_msg) + bool_val *= check_sub_profs(TP_C, sub_prof, msg) !bool_val && break end return bool_val end - -function check_sub_profs( - ::Type{TP}, - time_profile::RepresentativeProfile, - msg::String, -) where {TP<:RepresentativeProfile} - bool_val = true - for l1_profile ∈ time_profile.vals - sub_msg = "in representative profiles " * msg - bool_val *= check_not_profiles(TP, l1_profile, sub_msg) - !bool_val && break - end - return bool_val -end -function check_sub_profs( - ::Type{TP}, - time_profile::RepresentativeProfile, - msg::String, -) where {TP<:TimeProfile} - bool_val = true - for l1_profile ∈ time_profile.vals - sub_msg = "in representative profiles " * msg - bool_val *= check_not_profiles(TP, l1_profile, sub_msg) - bool_val *= check_sub_profs(TP, l1_profile, msg) - !bool_val && break - end - return bool_val -end - -function check_sub_profs( - ::Type{TP}, - time_profile::ScenarioProfile, - msg::String, -) where {TP<:ScenarioProfile} - bool_val = true - for l1_profile ∈ time_profile.vals - sub_msg = "in scenario profiles " * msg - bool_val *= check_not_profiles(TP, l1_profile, sub_msg) - !bool_val && break - end - return bool_val -end -function check_sub_profs( - ::Type{TP}, - time_profile::ScenarioProfile, - msg::String, -) where {TP<:TimeProfile} - bool_val = true - for l1_profile ∈ time_profile.vals - sub_msg = "in scenario profiles " * msg - bool_val *= check_not_profiles(TP, l1_profile, sub_msg) - bool_val *= check_sub_profs(TP, l1_profile, sub_msg) - !bool_val && break - end - return bool_val -end - -function check_sub_profs( - ::Type{TP}, - time_profile::PartitionProfile, - msg::String, -) where {TP<:PartitionProfile} - bool_val = true - for l1_profile ∈ time_profile.vals - sub_msg = "in partition profiles " * msg - bool_val *= check_not_profiles(TP, l1_profile, sub_msg) - !bool_val && break - end - return bool_val -end - function check_sub_profs( - ::Type{TP}, - time_profile::FixedProfile, + ::Type{TP_C}, + prof::TP, msg::String, -) where {TP<:TimeProfile} +) where {TP_C<:TimeProfile, TP<:Union{FixedProfile, Number}} return true end @@ -848,7 +777,7 @@ function check_strategic_profile(time_profile::TimeProfile, message::String) bool_sp = check_sub_profs(typeof(time_profile), time_profile, message) else # Check the profiles - bool_sp = check_not_profiles(StrategicProfile, time_profile, message) + bool_sp = check_sub_profile(StrategicProfile, time_profile, message) end return bool_sp @@ -880,7 +809,7 @@ function check_representative_profile(time_profile::TimeProfile, message::String bool_rp = check_sub_profs(RepresentativeProfile, time_profile, message) else # Check the profiles - bool_rp = check_not_profiles(RepresentativeProfile, time_profile, message) + bool_rp = check_sub_profile(RepresentativeProfile, time_profile, message) end return bool_rp end @@ -904,7 +833,7 @@ function check_scenario_profile(time_profile::TimeProfile, message::String) bool_scp = check_sub_profs(ScenarioProfile, time_profile, message) else # Check the profiles - bool_scp = check_not_profiles(ScenarioProfile, time_profile, message) + bool_scp = check_sub_profile(ScenarioProfile, time_profile, message) end return bool_scp @@ -929,7 +858,7 @@ function check_partition_profile(time_profile::TimeProfile, message::String) bool_part = check_sub_profs(PartitionProfile, time_profile, message) else # Check the profiles - bool_part = check_not_profiles(ScenarioProfile, time_profile, message) + bool_part = check_sub_profile(ScenarioProfile, time_profile, message) end return bool_part From efa5a2e844d8e7a0ea98d81098509d473af9bb96 Mon Sep 17 00:00:00 2001 From: Julian Straus Date: Sun, 28 Jun 2026 13:28:21 +0200 Subject: [PATCH 8/8] Updated NEWS.md and version numbers --- NEWS.md | 9 ++++++++- Project.toml | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 512d4dd..f02fac7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,12 @@ # Release notes +## Version 0.10.5 (2026-06-28) + +* Increased robustness for checking whether a `TimeProfile` is of the correct type for indexing. +* Added support in checks for `PartitionProfile`: + * This support is only included for indexing. + * Support for check of length infeasible due to the problems related potentially differing partitions. + ## Version 0.10.4 (2026-06-15) ### Bug fixes @@ -26,7 +33,7 @@ ### Minor updates -* Add support for JuMP.AbstractModel to allow custom model types, e.g. for decomposition. +* Add support for JuMP.AbstractModel to allow custom model types, *e.g.*, for decomposition. ### Bug fixes diff --git a/Project.toml b/Project.toml index c8c2711..7a26183 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.4" +version = "0.10.5" [deps] JuMP = "4076af6c-e467-56ae-b986-b466b2749572"