diff --git a/src/time-resolution.jl b/src/time-resolution.jl index 42a7197b..fd89823a 100644 --- a/src/time-resolution.jl +++ b/src/time-resolution.jl @@ -55,7 +55,7 @@ function resolution_matrix( end """ - rp_periods = compute_rp_periods(array_time_steps) + rp_periods = compute_rp_periods(array_time_steps; strategy = :greedy) Given the time steps of various flows/assets in the `array_time_steps` input, compute the representative period splits. Each element of `array_time_steps` is an array of ranges with the following assumptions: @@ -68,7 +68,13 @@ Each element of `array_time_steps` is an array of ranges with the following assu Notice that this implies that they form a disjunct partition of `1:N`. The output will also be an array of ranges with the conditions above. -The output is constructed greedily, i.e., it selects the next largest breakpoint following the algorithm below: + +## Strategies + +### :greedy + +If `strategy = :greedy` (default), then the output is constructed greedily, +i.e., it selects the next largest breakpoint following the algorithm below: 0. Input: `Vᴵ₁, …, Vᴵₚ`, a list of time step ranges. Each element of `Vᴵⱼ` is a range `r = r.start:r.end`. Output: `V`. 1. Compute the end of the representative period `N` (all `Vᴵⱼ` should have the same end) @@ -80,7 +86,7 @@ The output is constructed greedily, i.e., it selects the next largest breakpoint 7. If `e = N`, then END 8. Otherwise, define `s = e + 1` and go to step 4. -## Examples +#### Examples ```jldoctest time_steps1 = [1:4, 5:8, 9:12] @@ -109,10 +115,58 @@ compute_rp_periods([time_steps1, time_steps2]) 7:10 11:12 ``` + +### :all + +If `strategy = :all`, then the output selects includes all the breakpoints from the input. +Another way of describing it, is to select the minimum end-point instead of the maximum end-point in the `:greedy` strategy. + +#### Examples + +```jldoctest +time_steps1 = [1:4, 5:8, 9:12] +time_steps2 = [1:3, 4:6, 7:9, 10:12] +compute_rp_periods([time_steps1, time_steps2]; strategy = :all) + +# output + +6-element Vector{UnitRange{Int64}}: + 1:3 + 4:4 + 5:6 + 7:8 + 9:9 + 10:12 +``` + +```jldoctest +time_steps1 = [1:1, 2:3, 4:6, 7:10, 11:12] +time_steps2 = [1:2, 3:4, 5:5, 6:7, 8:9, 10:12] +compute_rp_periods([time_steps1, time_steps2]; strategy = :all) + +# output + +10-element Vector{UnitRange{Int64}}: + 1:1 + 2:2 + 3:3 + 4:4 + 5:5 + 6:6 + 7:7 + 8:9 + 10:10 + 11:12 +``` """ function compute_rp_periods( - array_time_steps::AbstractVector{<:AbstractVector{<:UnitRange{<:Integer}}}, + array_time_steps::AbstractVector{<:AbstractVector{<:UnitRange{<:Integer}}}; + strategy = :greedy, ) + valid_strategies = [:greedy, :all] + if !(strategy in valid_strategies) + error("`strategy` should be one of $valid_strategies. See docs for more info.") + end # Get Vᴵ₁, the last range of it, the last element of the range representative_period_end = array_time_steps[1][end][end] for time_steps in array_time_steps @@ -121,18 +175,31 @@ function compute_rp_periods( @assert representative_period_end == time_steps[end][end] end rp_periods = UnitRange{Int}[] # List of ranges - period_start = 1 - while period_start < representative_period_end - # The first range end larger than period_start for each range in each time_steps. - breakpoints = ( - first(r[end] for r in time_steps if r[end] ≥ period_start) for - time_steps in array_time_steps - ) - period_end = maximum(breakpoints) - @assert period_end ≥ period_start - push!(rp_periods, period_start:period_end) - period_start = period_end + 1 + period_start = 1 + if strategy == :greedy + while period_start < representative_period_end + # The first range end larger than period_start for each range in each time_steps. + breakpoints = ( + first(r[end] for r in time_steps if r[end] ≥ period_start) for + time_steps in array_time_steps + ) + period_end = maximum(breakpoints) + @assert period_end ≥ period_start + push!(rp_periods, period_start:period_end) + period_start = period_end + 1 + end + elseif strategy == :all + # We need all end points of each interval + end_points_per_array = map(array_time_steps) do x # For each set of time_steps + last.(x) # Retrieve the last element of each interval + end + # Then we concatenate, remove duplicates, and sort. + end_points = vcat(end_points_per_array...) |> unique |> sort + for period_end in end_points + push!(rp_periods, period_start:period_end) + period_start = period_end + 1 + end end return rp_periods end diff --git a/test/test-time-resolution.jl b/test/test-time-resolution.jl index b54a5b41..ed566d3b 100644 --- a/test/test-time-resolution.jl +++ b/test/test-time-resolution.jl @@ -38,15 +38,47 @@ time_steps2 = [1:3, 4:6, 7:9, 10:12] # every 3 hours time_steps3 = [i:i for i ∈ 1:12] # hourly - @test compute_rp_periods([time_steps1, time_steps2]) == time_steps1 - @test compute_rp_periods([time_steps1, time_steps2, time_steps3]) == time_steps1 - @test compute_rp_periods([time_steps2, time_steps3]) == time_steps2 + @testset "strategy greedy (default)" begin + @test compute_rp_periods([time_steps1, time_steps2]) == time_steps1 + @test compute_rp_periods([time_steps1, time_steps2, time_steps3]) == time_steps1 + @test compute_rp_periods([time_steps2, time_steps3]) == time_steps2 + end + + @testset "strategy all" begin + @test compute_rp_periods([time_steps1, time_steps2]; strategy = :all) == + [1:3, 4:4, 5:6, 7:8, 9:9, 10:12] + @test compute_rp_periods( + [time_steps1, time_steps2, time_steps3]; + strategy = :all, + ) == time_steps3 + @test compute_rp_periods([time_steps2, time_steps3]; strategy = :all) == + time_steps3 + end # Irregular time_steps4 = [1:6, 7:9, 10:11, 12:12] time_steps5 = [1:2, 3:4, 5:12] - @test compute_rp_periods([time_steps1, time_steps4]) == [1:6, 7:9, 10:12] - @test compute_rp_periods([time_steps1, time_steps5]) == [1:4, 5:12] - @test compute_rp_periods([time_steps4, time_steps5]) == [1:6, 7:12] + + @testset "strategy greedy (default)" begin + @test compute_rp_periods([time_steps1, time_steps4]) == [1:6, 7:9, 10:12] + @test compute_rp_periods([time_steps1, time_steps5]) == [1:4, 5:12] + @test compute_rp_periods([time_steps4, time_steps5]) == [1:6, 7:12] + end + + @testset "strategy all" begin + @test compute_rp_periods([time_steps1, time_steps4]; strategy = :all) == + [1:4, 5:6, 7:8, 9:9, 10:11, 12:12] + @test compute_rp_periods([time_steps1, time_steps5]; strategy = :all) == + [1:2, 3:4, 5:8, 9:12] + @test compute_rp_periods([time_steps4, time_steps5]; strategy = :all) == + [1:2, 3:4, 5:6, 7:9, 10:11, 12:12] + end + + @testset "Bad strategy" begin + @test_throws ErrorException compute_rp_periods( + [time_steps1, time_steps2], + strategy = :bad, + ) + end end end