diff --git a/NEWS.md b/NEWS.md index 2131721..db06858 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,10 @@ # Release notes +## Unversioned + +### Bugfix + +* Fixed a bug in the functionality `get_all_periods` when using `RepresentativePeriods` or `OperationalScenarios`. ## Version 0.7.2 (2026-08-04) @@ -68,7 +73,6 @@ * Improved testing of `descriptive_names` and added more names from other packages. * Improved documentation. - ## Version 0.6.0 (2025-12-15) ### Bugfix @@ -336,6 +340,7 @@ * Relaxed redundant minor version specification in the `Project.toml`-file. ### Feature + * Provided the user with the ability to provide descriptive_names in the folder `ext/EMGUIExt/descriptive_names.yml` for a EMX package, and/or a user defined file, and/or a user defined Dict. diff --git a/src/utils_GUI/GUI_utils.jl b/src/utils_GUI/GUI_utils.jl index 08f5231..c68741a 100644 --- a/src/utils_GUI/GUI_utils.jl +++ b/src/utils_GUI/GUI_utils.jl @@ -82,7 +82,7 @@ end pick_component!(gui::GUI, ::Nothing, ax_type::Symbol) Check if a system is found under the mouse pointer and if it is an `AbstractGUIObj` (for -objects in the topology axis) or a `Dict` (for objects in the results axis). If found, +objects in the topology axis) or a `Dict` (for objects in the results axis). If found, state variables are updated. Results in the topology axis are only cleared if `ax_type = :topo` and in the results axis if `ax_type = :results`. """ @@ -108,7 +108,7 @@ end """ clear_selection!(gui::GUI, ax_type::Symbol) -Clear the color selection of the topology axis if `ax_type = :topo`, and of the results axis +Clear the color selection of the topology axis if `ax_type = :topo`, and of the results axis if `ax_type = :results`. """ function clear_selection!(gui::GUI, ax_type::Symbol) @@ -407,7 +407,7 @@ function initialize_available_data!(gui) end end - # Add additional plots provided by the user + # Add additional plots provided by the user element = nothing # Additional plots are not associated with a specific element for (i, additional_plot) ∈ enumerate(get_var(gui, :additional_plots)) df = get_data(additional_plot["data"]) @@ -599,8 +599,8 @@ get_values(vals::TimeProfile, ts::Vector) = vals[ts] """ get_inv_objs(obj::AbstractElement) -Get the objects for which investment information should be stored for a given `AbstractElement`. -By default, this is just the element itself, but for some elements (e.g., `Transmission`) it can +Get the objects for which investment information should be stored for a given `AbstractElement`. +By default, this is just the element itself, but for some elements (e.g., `Transmission`) it can be relevant to also store the investment information of related objects (e.g., its modes). """ get_inv_objs(obj::AbstractElement) = [obj] @@ -827,8 +827,8 @@ end """ select_data!(gui::GUI, name::String; selection::Vector = Any[], fun::Function = findfirst) -Select the data with name `name` from the `available_data` menu. If `selection` is provided, -it is used to further specify which data to select. The `fun` argument is used to specify the +Select the data with name `name` from the `available_data` menu. If `selection` is provided, +it is used to further specify which data to select. The `fun` argument is used to specify the function for finding the data in the menu (default is `findfirst`). """ function select_data!( @@ -880,40 +880,51 @@ Get all TimeStructures in `𝒯` as a dictionary with their string representatio """ function get_all_periods(𝒯::TimeStructure) all_periods = Union{TS.TimePeriod,TS.TimeStructure}[] - get_all_periods!(all_periods, 𝒯) + get_all_periods!(all_periods, 𝒯, 𝒯) return get_repr_dict(unique(all_periods)) end """ - get_all_periods!(vec::Vector, ts::TwoLevel) - get_all_periods!(vec::Vector, ts::RepresentativePeriods) - get_all_periods!(vec::Vector, ts::OperationalScenarios) - get_all_periods!(vec::Vector, ts::Any) + get_all_periods!(vec::Vector, per, ::TwoLevel) + get_all_periods!(vec::Vector, per, ::RepresentativePeriods) + get_all_periods!(vec::Vector, per, ::OperationalScenarios) + get_all_periods!(vec::Vector, per, ::SimpleTimes) + get_all_periods!(::Vector, per, ::Any) -Get all TimeStructures in `ts` and append them to `vec`. +Get all `TimeStructure`s in `per` and append them to `vec`. The required time structures +are automatically deduced from the internal structure of `per`. """ -function get_all_periods!(vec::Vector, ts::TwoLevel) - append!(vec, collect(ts)) - append!(vec, strategic_periods(ts)) - for t ∈ ts.operational - get_all_periods!(vec, t) +function get_all_periods!(vec::Vector, per, ::TwoLevel) + 𝒯ᴵⁿᵛ = strategic_periods(per) + append!(vec, 𝒯ᴵⁿᵛ) + for t_inv ∈ 𝒯ᴵⁿᵛ + get_all_periods!(vec, t_inv, t_inv.operational) end end -function get_all_periods!(vec::Vector, ts::RepresentativePeriods) - append!(vec, repr_periods(ts)) - for t ∈ ts.rep_periods - get_all_periods!(vec, t) +function get_all_periods!(vec::Vector, per, ::RepresentativePeriods) + 𝒯ʳᵖ = repr_periods(per) + append!(vec, 𝒯ʳᵖ) + for t_rp ∈ 𝒯ʳᵖ + ts = + isa(per, TS.TimeStructurePeriod) ? t_rp.operational.operational : + t_rp.operational + get_all_periods!(vec, t_rp, ts) end end -function get_all_periods!(vec::Vector, ts::OperationalScenarios) - append!(vec, opscenarios(ts)) - for t ∈ ts.scenarios - get_all_periods!(vec, t) +function get_all_periods!(vec::Vector, per, ::OperationalScenarios) + 𝒯ˢᶜ = opscenarios(per) + append!(vec, 𝒯ˢᶜ) + for t_scp ∈ 𝒯ˢᶜ + ts = + isa(per, TS.TimeStructurePeriod) ? t_scp.operational.operational : + t_scp.operational + get_all_periods!(vec, t_scp, ts) end end -function get_all_periods!(::Vector, ::Any) - return nothing +function get_all_periods!(vec::Vector, per, ::SimpleTimes) + append!(vec, collect(per)) end +get_all_periods!(::Vector, per, ::Any) = nothing """ get_repr_dict(vec::AbstractVector{T}) where T @@ -981,7 +992,7 @@ end """ read_csv(file::String) -Read a CSV file and return it as a DataFrame. The time column is renamed to :t if it is +Read a CSV file and return it as a DataFrame. The time column is renamed to :t if it is named "t", "sp", "rp", "osc", or "op". """ function read_csv(file::String) @@ -993,7 +1004,7 @@ end """ rename_time_column!(df::DataFrame) -Rename the time column in `df` to :t if it is named "t", "sp", "rp", "osc", or "op". +Rename the time column in `df` to :t if it is named "t", "sp", "rp", "osc", or "op". If more than one of these columns are present, an error is thrown. """ function rename_time_column!(df::DataFrame) diff --git a/test/runtests.jl b/test/runtests.jl index cfcc371..907e14f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -40,8 +40,8 @@ global_logger(logger_new) # Check if there is need for formatting include(joinpath(testdir, "JuliaFormatter.jl")) - # The following tests simply checks if the main examples can be run without errors - include(joinpath(testdir, "test_examples.jl")) + # Test some of the utility functionality + include(joinpath(testdir, "test_utils.jl")) # The following tests results input and output functionality (saving and loading results) include(joinpath(testdir, "test_results_IO.jl")) @@ -54,6 +54,9 @@ global_logger(logger_new) # Test descriptive names functionalities include(joinpath(testdir, "test_descriptive_names.jl")) + + # The following tests simply checks if the main examples can be run without errors + include(joinpath(testdir, "test_examples.jl")) end end global_logger(logger_org) diff --git a/test/test_utils.jl b/test/test_utils.jl new file mode 100644 index 0000000..9921d3b --- /dev/null +++ b/test/test_utils.jl @@ -0,0 +1,67 @@ +@testset "CSV utilities" begin + # Simplified function for testing + function test_pers(opers, pers_dict) + @test isempty(setdiff(repr.(opers), keys(pers_dict))) + @test isempty(setdiff(opers, values(pers_dict))) + end + + # Test for SimpleTimes + ts_opers = SimpleTimes(4, 1) + pers_dict = EMGUI.get_all_periods(ts_opers) + test_pers(collect(ts_opers), pers_dict) + @test length(pers_dict) == 4 + + # Test for OperationalScenarios + ts_oscs = OperationalScenarios(2, ts_opers) + pers_dict = EMGUI.get_all_periods(ts_oscs) + test_pers(collect(ts_oscs), pers_dict) + test_pers(opscenarios(ts_oscs), pers_dict) + @test length(pers_dict) == (4 + 1) * 2 + + # Test for RepresentativePeriods{SimpleTimes} + ts_rp = RepresentativePeriods(2, 8760, ts_opers) + pers_dict = EMGUI.get_all_periods(ts_rp) + test_pers(collect(ts_rp), pers_dict) + test_pers(repr_periods(ts_rp), pers_dict) + @test length(pers_dict) == (4 + 1) * 2 + + # Test for RepresentativePeriods{OperationalScenarios} + ts_rp_oscs = RepresentativePeriods(2, 8760, ts_oscs) + pers_dict = EMGUI.get_all_periods(ts_rp_oscs) + test_pers(collect(ts_rp_oscs), pers_dict) + test_pers(opscenarios(ts_rp_oscs), pers_dict) + test_pers(repr_periods(ts_rp_oscs), pers_dict) + @test length(pers_dict) == ((4 + 1) * 2 + 1) * 2 + + # Test for TwoLevel{SimpleTimes} + ts_tl = TwoLevel(2, 1, ts_opers) + pers_dict = EMGUI.get_all_periods(ts_tl) + test_pers(collect(ts_tl), pers_dict) + test_pers(strat_periods(ts_tl), pers_dict) + @test length(pers_dict) == (4 + 1) * 2 + + # Test for TwoLevel{OperationalScenarios} + ts_tl_oscs = TwoLevel(2, 1, ts_oscs) + pers_dict = EMGUI.get_all_periods(ts_tl_oscs) + test_pers(collect(ts_tl_oscs), pers_dict) + test_pers(opscenarios(ts_tl_oscs), pers_dict) + test_pers(strat_periods(ts_tl_oscs), pers_dict) + @test length(pers_dict) == ((4 + 1) * 2 + 1) * 2 + + # Test for TwoLevel{RepresentativePeriods} + ts_tl_rp = TwoLevel(2, 1, ts_rp) + pers_dict = EMGUI.get_all_periods(ts_tl_rp) + test_pers(collect(ts_tl_rp), pers_dict) + test_pers(repr_periods(ts_tl_rp), pers_dict) + test_pers(strat_periods(ts_tl_rp), pers_dict) + @test length(pers_dict) == ((4 + 1) * 2 + 1) * 2 + + # Test for TwoLevel{RepresentativePeriods{OperationalScenarios}} + ts_tl_rp_oscs = TwoLevel(2, 1, ts_rp_oscs) + pers_dict = EMGUI.get_all_periods(ts_tl_rp_oscs) + test_pers(collect(ts_tl_rp_oscs), pers_dict) + test_pers(opscenarios(ts_tl_rp_oscs), pers_dict) + test_pers(repr_periods(ts_tl_rp_oscs), pers_dict) + test_pers(strat_periods(ts_tl_rp_oscs), pers_dict) + @test length(pers_dict) == (((4 + 1) * 2 + 1) * 2 + 1) * 2 +end