Skip to content

Commit f8283db

Browse files
authored
Inclusion of checks support for PartitionProfile (#95)
* Rewrote existing profile checks for reusability * Reworked strategic profile checks * Reworked representative profile checks * Reworked scenario profile checks * Added partition profile checks * Simplified function work flow for profile checks
1 parent 586386b commit f8283db

5 files changed

Lines changed: 278 additions & 129 deletions

File tree

NEWS.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Release notes
22

3+
## Version 0.10.5 (2026-06-28)
4+
5+
* Increased robustness for checking whether a `TimeProfile` is of the correct type for indexing.
6+
* Added support in checks for `PartitionProfile`:
7+
* This support is only included for indexing.
8+
* Support for check of length infeasible due to the problems related potentially differing partitions.
9+
310
## Version 0.10.4 (2026-06-15)
411

512
### Bug fixes
@@ -26,7 +33,7 @@
2633

2734
### Minor updates
2835

29-
* Add support for JuMP.AbstractModel to allow custom model types, e.g. for decomposition.
36+
* Add support for JuMP.AbstractModel to allow custom model types, *e.g.*, for decomposition.
3037

3138
### Bug fixes
3239

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "EnergyModelsBase"
22
uuid = "5d7e687e-f956-46f3-9045-6f5a5fd49f50"
33
authors = ["Lars Hellemo <Lars.Hellemo@sintef.no>, Julian Straus <Julian.Straus@sintef.no>"]
4-
version = "0.10.4"
4+
version = "0.10.5"
55

66
[deps]
77
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
@@ -18,5 +18,5 @@ EMIExt = "EnergyModelsInvestments"
1818
EnergyModelsInvestments = "0.9"
1919
JuMP = "1"
2020
SparseVariables = "0.7.3"
21-
TimeStruct = "0.9.11"
21+
TimeStruct = "0.9.12"
2222
julia = "1.10"

docs/src/library/internals/functions.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ check_profile
6767
check_strategic_profile
6868
check_representative_profile
6969
check_scenario_profile
70+
check_partition_profile
71+
check_sub_profs
72+
check_sub_profile
73+
check_not_profile
7074
compile_logs
7175
```
7276

src/checks.jl

Lines changed: 176 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,126 @@ function check_profile(
639639
end
640640
check_profile(fieldname, value, ts, p_msg) = nothing
641641

642+
"""
643+
check_not_profile(::Type{TP}, sub_prof::TimeProfile, sub_msg::String) where {TP<:TimeProfile}
644+
645+
Check that the profile `sub_prof` is not of type `TP`.
646+
"""
647+
function check_not_profile(::Type{TP}, sub_prof::TimeProfile, sub_msg::String) where {TP<:TimeProfile}
648+
bool_tp = !isa(sub_prof, TP)
649+
@assert_or_log(bool_tp, "`$(TP)`s " * sub_msg)
650+
return bool_tp
651+
end
652+
653+
"""
654+
check_sub_profile(::Type{TP}, sub_prof::TimeProfile, sub_msg::String) where {T<:TimeProfile}
655+
656+
Check that the time profile `sub_prof` is applicable for indexing with `TimeStructurePeriod`
657+
or `PeriodPartition` as declared by the time profile type `TP`. The check is not performed
658+
on potential subprofiles. This can be achieved through the function [`check_sub_profs`](@ref)
659+
660+
!!! danger "Usage of this function"
661+
User should never use this function directly. It should only be included within the core
662+
structure in `EnergyModelsBase`.
663+
"""
664+
function check_sub_profile(::Type{TP}, sub_prof::TimeProfile, sub_msg::String) where {TP<:Union{StrategicProfile, StrategicStochasticProfile}}
665+
bool_op = check_not_profile(OperationalProfile, sub_prof, sub_msg)
666+
bool_part = check_not_profile(PartitionProfile, sub_prof, sub_msg)
667+
bool_scp = check_not_profile(ScenarioProfile, sub_prof, sub_msg)
668+
bool_rp = check_not_profile(RepresentativeProfile, sub_prof, sub_msg)
669+
670+
return bool_op * bool_part * bool_scp * bool_rp
671+
end
672+
function check_sub_profile(::Type{TP}, sub_prof::TimeProfile, sub_msg::String) where {TP<:RepresentativeProfile}
673+
bool_op = check_not_profile(OperationalProfile, sub_prof, sub_msg)
674+
bool_part = check_not_profile(PartitionProfile, sub_prof, sub_msg)
675+
bool_scp = check_not_profile(ScenarioProfile, sub_prof, sub_msg)
676+
677+
return bool_op * bool_part * bool_scp
678+
end
679+
function check_sub_profile(::Type{TP}, sub_prof::TimeProfile, sub_msg::String) where {TP<:ScenarioProfile}
680+
bool_op = check_not_profile(OperationalProfile, sub_prof, sub_msg)
681+
bool_part = check_not_profile(PartitionProfile, sub_prof, sub_msg)
682+
683+
return bool_op * bool_part
684+
end
685+
function check_sub_profile(::Type{TP}, sub_prof::TimeProfile, sub_msg::String) where {TP<:PartitionProfile}
686+
bool_op = check_not_profile(OperationalProfile, sub_prof, sub_msg)
687+
return bool_op
688+
end
689+
function check_sub_profile(::Type{TP}, sub_prof::Number, sub_msg::String) where {TP<:PartitionProfile}
690+
return true
691+
end
692+
693+
"""
694+
check_sub_profs(check_type::Type{TP_C}, prof::TP, msg::String) where {TP_C<:TimeProfile, TP<:TimeProfile}
695+
696+
Check that the provided `prof` allows indexing as specified through the provided
697+
`TP_C` profile type. Each sub profile is checked if the `prof` includes sub profiles
698+
different than `FixedProfile`. The check is based on the provided profile type `TP_C`.
699+
700+
!!! danger "Usage of these functions"
701+
User should never use these functions directly. They should only be included within the
702+
core structure in `EnergyModelsBase`.
703+
"""
704+
function check_sub_profs(::Type{TP_C}, prof::TP_C, msg::String) where {TP_C<:TimeProfile}
705+
bool_val = true
706+
sub_msg = "in `$(TP_C)`s " * msg
707+
for sub_prof prof.vals
708+
bool_val *= check_sub_profile(TP_C, sub_prof, sub_msg)
709+
!bool_val && break
710+
end
711+
return bool_val
712+
end
713+
function check_sub_profs(
714+
::Type{TP_C},
715+
prof::TP,
716+
msg::String,
717+
) where {TP_C<:TimeProfile, TP<:TimeProfile}
718+
bool_val = true
719+
sub_msg = "in `$(TP.name.name)`s " * msg
720+
for sub_prof prof.vals
721+
bool_val *= check_sub_profile(TP_C, sub_prof, sub_msg)
722+
bool_val *= check_sub_profs(TP_C, sub_prof, sub_msg)
723+
!bool_val && break
724+
end
725+
return bool_val
726+
end
727+
function check_sub_profs(
728+
::Type{TP_C},
729+
prof::TP_C,
730+
msg::String,
731+
) where {TP_C<:StrategicStochasticProfile}
732+
bool_val = true
733+
sub_msg = "in `StrategicStochasticProfile`s " * msg
734+
for sp_array prof.vals, sub_prof sp_array
735+
bool_val = check_sub_profile(TP_C, sub_prof, sub_msg)
736+
!bool_val && break
737+
end
738+
return bool_val
739+
end
740+
function check_sub_profs(
741+
::Type{TP_C},
742+
prof::StrategicStochasticProfile,
743+
msg::String,
744+
) where {TP_C<:TimeProfile}
745+
bool_val = true
746+
sub_msg = "in `StrategicStochasticProfile`s " * msg
747+
for sp_array prof.vals, sub_prof sp_array
748+
bool_val = check_sub_profile(TP_C, sub_prof, sub_msg)
749+
bool_val *= check_sub_profs(TP_C, sub_prof, msg)
750+
!bool_val && break
751+
end
752+
return bool_val
753+
end
754+
function check_sub_profs(
755+
::Type{TP_C},
756+
prof::TP,
757+
msg::String,
758+
) where {TP_C<:TimeProfile, TP<:Union{FixedProfile, Number}}
759+
return true
760+
end
761+
642762
"""
643763
check_strategic_profile(time_profile::TimeProfile, message::String)
644764
@@ -647,48 +767,22 @@ strategic indexing.
647767
648768
## Checks
649769
- `TimeProfile`s accessed in `StrategicPeriod`s cannot include `OperationalProfile`,
650-
`ScenarioProfile`, or `RepresentativeProfile` as this is not allowed through indexing
651-
on the `TimeProfile`.
770+
`PartitionProfile`, `ScenarioProfile`, or `RepresentativeProfile` as this is not allowed
771+
through indexing on the `TimeProfile`.
652772
"""
653773
function check_strategic_profile(time_profile::TimeProfile, message::String)
654-
# Check on the highest level
655-
bool_sp = check_strat_sub_profile(time_profile, message, true)
656-
657-
if isa(time_profile, StrategicProfile)
658-
for l1_profile time_profile.vals
659-
sub_msg = "in strategic profiles " * message
660-
bool_sp = check_strat_sub_profile(l1_profile, sub_msg, bool_sp)
661-
!bool_sp && break
662-
end
663-
elseif isa(time_profile, StrategicStochasticProfile)
664-
for sp_array time_profile.vals, l1_profile sp_array
665-
sub_msg = "in strategic stochastic profiles " * message
666-
bool_sp = check_strat_sub_profile(l1_profile, sub_msg, bool_sp)
667-
!bool_sp && break
668-
end
669-
end
670774

671-
return bool_sp
672-
end
673-
function check_strat_sub_profile(sub_profile::TimeProfile, sub_msg::String, bool_sp::Bool)
674-
@assert_or_log(
675-
!isa(sub_profile, OperationalProfile),
676-
"Operational profiles " * sub_msg
677-
)
678-
@assert_or_log(!isa(sub_profile, ScenarioProfile), "Scenario profiles " * sub_msg)
679-
@assert_or_log(
680-
!isa(sub_profile, RepresentativeProfile),
681-
"Representative profiles " * sub_msg
682-
)
775+
if isa(time_profile, Union{StrategicProfile, StrategicStochasticProfile})
776+
# Iterate through the strategic or strategic stochstic profiles, if existing
777+
bool_sp = check_sub_profs(typeof(time_profile), time_profile, message)
778+
else
779+
# Check the profiles
780+
bool_sp = check_sub_profile(StrategicProfile, time_profile, message)
781+
end
683782

684-
bool_sp *=
685-
!isa(sub_profile, OperationalProfile) &&
686-
!isa(sub_profile, ScenarioProfile) &&
687-
!isa(sub_profile, RepresentativeProfile)
688783
return bool_sp
689784
end
690785

691-
692786
"""
693787
check_representative_profile(time_profile::TimeProfile, message::String)
694788
@@ -700,44 +794,23 @@ representative periods indexing.
700794
- `message` - A message that should be printed after the type of profile.
701795
702796
## Checks
703-
- `TimeProfile`s accessed in `RepresentativePeriod`s cannot include `OperationalProfile`
704-
or `ScenarioProfile` as this is not allowed through indexing on the `TimeProfile`.
797+
- `TimeProfile`s accessed in `RepresentativePeriod`s cannot include `OperationalProfile`,
798+
`PartitionProfile` or `ScenarioProfile` as this is not allowed through indexing on the
799+
`TimeProfile`.
705800
"""
706801
function check_representative_profile(time_profile::TimeProfile, message::String)
707-
# Check on the highest level
708-
bool_rp = check_repr_sub_profile(time_profile, message, true)
709-
710-
# Iterate through the strategic profiles, if existing
711-
if isa(time_profile, StrategicProfile)
712-
for l1_profile time_profile.vals
713-
sub_msg_1 = "in strategic profiles " * message
714-
bool_rp = check_repr_sub_profile(l1_profile, sub_msg_1, bool_rp)
715-
if isa(l1_profile, RepresentativeProfile)
716-
for l2_profile l1_profile.vals
717-
sub_msg_2 = "in representative profiles " * sub_msg_1
718-
bool_rp = check_repr_sub_profile(l2_profile, sub_msg_2, bool_rp)
719-
end
720-
end
721-
end
722-
end
723802

724-
# Iterate through the representative profiles, if existing
725-
if isa(time_profile, RepresentativeProfile)
726-
for l1_profile time_profile.vals
727-
sub_msg = "in representative profiles " * message
728-
bool_rp = check_repr_sub_profile(l1_profile, sub_msg, bool_rp)
729-
end
730-
end
731-
return bool_rp
732-
end
733-
function check_repr_sub_profile(sub_profile::TimeProfile, sub_msg::String, bool_rp::Bool)
734-
@assert_or_log(
735-
!isa(sub_profile, OperationalProfile),
736-
"Operational profiles " * sub_msg
803+
if isa(
804+
time_profile,
805+
Union{StrategicProfile, StrategicStochasticProfile, RepresentativeProfile}
737806
)
738-
@assert_or_log(!isa(sub_profile, ScenarioProfile), "Scenario profiles " * sub_msg)
739-
740-
bool_rp *= !isa(sub_profile, OperationalProfile) && !isa(sub_profile, ScenarioProfile)
807+
# Iterate through the strategic, strategic stochstic, or representative profiles,
808+
# if existing
809+
bool_rp = check_sub_profs(RepresentativeProfile, time_profile, message)
810+
else
811+
# Check the profiles
812+
bool_rp = check_sub_profile(RepresentativeProfile, time_profile, message)
813+
end
741814
return bool_rp
742815
end
743816

@@ -748,68 +821,47 @@ Function for checking that an individual `TimeProfile` does not include the wron
748821
scenario indexing.
749822
750823
## Checks
751-
- `TimeProfile`s accessed in `OperationalScenario`s cannot include `OperationalProfile` as
752-
this is not allowed through indexing on the `TimeProfile`.
824+
- `TimeProfile`s accessed in `OperationalScenario`s cannot include `OperationalProfile` or
825+
`PartitionProfile` as this is not allowed through indexing on the `TimeProfile`.
753826
"""
754827
function check_scenario_profile(time_profile::TimeProfile, message::String)
755-
# Check on the highest level
756-
bool_scp = check_osc_sub_profile(time_profile, message, true)
757-
758-
# Iterate through the strategic profiles, if existing
759-
if isa(time_profile, StrategicProfile)
760-
for l1_profile time_profile.vals
761-
sub_msg_1 = "in strategic profiles " * message
762-
bool_scp = check_osc_sub_profile(l1_profile, sub_msg_1, bool_scp)
763-
if isa(l1_profile, RepresentativeProfile)
764-
sub_msg_2 = "in representative profiles " * sub_msg_1
765-
for l2_profile l1_profile.vals
766-
bool_scp = check_osc_sub_profile(l2_profile, sub_msg_2, bool_scp)
767-
if isa(l2_profile, ScenarioProfile)
768-
sub_msg_3 = "in scenario profiles in " * sub_msg_2
769-
for l3_profile l2_profile.vals
770-
bool_scp = check_osc_sub_profile(l3_profile, sub_msg_3, bool_scp)
771-
end
772-
end
773-
end
774-
elseif isa(l1_profile, ScenarioProfile)
775-
for l2_profile l1_profile.vals
776-
sub_msg_2 = "in scenario profiles " * sub_msg_1
777-
bool_scp = check_osc_sub_profile(l2_profile, sub_msg_2, bool_scp)
778-
end
779-
end
780-
end
781-
end
782-
783-
# Iterate through the representative profiles, if existing
784-
if isa(time_profile, RepresentativeProfile)
785-
for l1_profile time_profile.vals
786-
sub_msg_1 = "in representative profiles " * message
787-
bool_scp = check_osc_sub_profile(l1_profile, sub_msg_1, bool_scp)
788-
if isa(l1_profile, ScenarioProfile)
789-
for l2_profile l1_profile.vals
790-
sub_msg_2 = "in scenario profiles " * sub_msg_1
791-
bool_scp = check_osc_sub_profile(l2_profile, sub_msg_2, bool_scp)
792-
end
793-
end
794-
end
828+
# Check the sub structures
829+
if isa(
830+
time_profile,
831+
Union{StrategicProfile, StrategicStochasticProfile, RepresentativeProfile, ScenarioProfile}
832+
)
833+
bool_scp = check_sub_profs(ScenarioProfile, time_profile, message)
834+
else
835+
# Check the profiles
836+
bool_scp = check_sub_profile(ScenarioProfile, time_profile, message)
795837
end
796838

797-
# Iterate through the scenario profiles, if existing
798-
if isa(time_profile, ScenarioProfile)
799-
for l1_profile time_profile.vals
800-
sub_msg = "in scenario profiles " * message
801-
bool_scp = check_osc_sub_profile(l1_profile, sub_msg, bool_scp)
802-
end
803-
end
804839
return bool_scp
805840
end
806-
function check_osc_sub_profile(sub_profile::TimeProfile, sub_msg::String, bool_scp::Bool)
807-
@assert_or_log(
808-
!isa(sub_profile, OperationalProfile),
809-
"Operational profiles " * sub_msg
841+
842+
"""
843+
check_partition_profile(time_profile::TimeProfile, message::String)
844+
845+
Function for checking that an individual `TimeProfile` does not include the wrong type for
846+
partition indexing.
847+
848+
## Checks
849+
- `TimeProfile`s accessed in `PeriodPartion`s cannot include `OperationalProfile` as this is
850+
not allowed through indexing on the `TimeProfile`.
851+
"""
852+
function check_partition_profile(time_profile::TimeProfile, message::String)
853+
# Check the sub structures
854+
if isa(
855+
time_profile,
856+
Union{StrategicProfile, StrategicStochasticProfile, RepresentativeProfile, ScenarioProfile, PartitionProfile}
810857
)
811-
bool_scp *= !isa(sub_profile, OperationalProfile)
812-
return bool_scp
858+
bool_part = check_sub_profs(PartitionProfile, time_profile, message)
859+
else
860+
# Check the profiles
861+
bool_part = check_sub_profile(ScenarioProfile, time_profile, message)
862+
end
863+
864+
return bool_part
813865
end
814866

815867
"""

0 commit comments

Comments
 (0)