Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add intersect method #496

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 73 additions & 6 deletions src/timeaxis/timegrid.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mutable struct TimeGrid{T,P,L} <: AbstractTimeAxis{T}
n′ = convert(Int, n)
p′ = convert(P, p)

(n′ < 1) && throw(DomainError(n′))
(n′ < 0) && throw(DomainError(n′))
new(o′, p′, n′)
end

Expand Down Expand Up @@ -58,21 +58,32 @@ Base.eltype(::Type{<:TimeGrid{T}}) where T = T
Base.length(tg::TimeGrid{T,P,:finite}) where {T,P} = tg.n
Base.size(tg::TimeGrid{T,P,:finite}) where{T,P} = tg.n

###############################################################################
# Empty TimeGrid
###############################################################################

Base.isempty(tg::TimeGrid{T,P,:finite}) where {T,P} = iszero(tg.n)
Base.isempty(tg::TimeGrid{T,P,:infinite}) where {T,P} = false

Base.isfinite(tg::TimeGrid{T,P,:finite}) where {T,P} = true
Base.isfinite(tg::TimeGrid{T,P,:infinite}) where {T,P} = false

###############################################################################
# Printing
###############################################################################

Base.show(io::IO, tg::TimeGrid{T,P,:finite}) where {T,P} =
print(io, "$(tg.o) … $(tg[end]) / $(tg.p) ")
Base.show(io::IO, tg::TimeGrid{T,P,:finite}) where {T,P} =
isempty(tg) ? print(io, "(empty)") : print(io, "$(tg.o) … $(tg[end]) / $(tg.p)")

function Base.show(io::IO, ::MIME{Symbol("text/plain")}, tg::TimeGrid{T,P,:finite}) where {T,P}
summary(io, tg)
isempty(tg) && return
println(io, ":")
print(io, " $(tg.o)\n",
" ⋮\n",
" $(tg[end])\n",
" / $(tg.p)")
" ⋮\n",
" $(tg[end])\n",
" / $(tg.p)")
return
guilhermebodin marked this conversation as resolved.
Show resolved Hide resolved
end

Base.summary(io::IO, tg::TimeGrid{T,P,:infinite}) where {T,P} =
Expand Down Expand Up @@ -287,6 +298,62 @@ Base.resize!(tg::TimeGrid{T,P,:infinite}, n::Int) where {T,P} = tg

Base.diff(tg::TimeGrid{T,P,:finite}) where {T,P} = fill(tg.p, tg.n - 1)

###############################################################################
# Intersect
###############################################################################
function Base.intersect(tg1::TimeGrid, tgs...)
for tg in tgs
tg1 = intersect(tg1, tg)
end
return tg1
end
# TODO
# Consider checking if they are equal
function intersect(tg_inf::TimeGrid{T,P,:infinite},
tg_fin::TimeGrid{T,P,:finite})::TimeGrid{T,P,:finite} where {T,P}
if !assert_intersection_non_empty(tg_fin, tg_inf)
return TimeGrid(tg_fin; n = 0)
end
new_o = max(tg_fin.o, tg_inf.o)
iblislin marked this conversation as resolved.
Show resolved Hide resolved
new_n = tg_inf.o > tg_fin.o ? findfirst(isequal(tg_fin[end]), tg_inf) : tg_fin.n
return TimeGrid(new_o, tg_fin.p, new_n)
end
intersect(tg_fin::TimeGrid{T,P,:finite}, tg_inf::TimeGrid{T,P,:infinite}) where {T,P} = intersect(tg_inf, tg_fin)
function intersect(tg_inf1::TimeGrid{T,P,:infinite}, tg_inf2::TimeGrid{T,P,:infinite}) where {T,P}
if !assert_intersection_non_empty(tg_inf1, tg_inf2)
return TimeGrid(tg_inf1.o, tg_inf1.p, 0)
end
new_o = max(tg_inf1.o, tg_inf2.o)
return TimeGrid(new_o, tg_inf1.p)
end
function intersect(tg_fin1::TimeGrid{T,P,:finite}, tg_fin2::TimeGrid{T,P,:finite}) where {T,P}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found a case that might yield empty TimeGrid:

julia> tg = TimeGrid(DateTime(2021, 1, 1, 0), Hour(1), 24)
24-element TimeGrid{DateTime, Hour, :finite}:
 2021-01-01T00:00:00
  
 2021-01-01T23:00:00
 / 1 hour

julia> tg′ = TimeGrid(DateTime(2021, 1, 1, 0, 30), Hour(1), 24)
24-element TimeGrid{DateTime, Hour, :finite}:
 2021-01-01T00:30:00
  
 2021-01-01T23:30:00
 / 1 hour

julia> intersect(collect(tg), collect(tg′))
DateTime[]

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact I was wrong on slack there is a way to infinite with infinite intersection being empty

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rewrote some parts and added more tests.

The current behavior of intersecting tg with different periods is method error. What do you think about that?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current behavior of intersecting tg with different periods is method error. What do you think about that?

That might need IrregularTimeGrid implemented.
I think we can revisit it later, putting a TODO here is fine for me.

I still don't make a perfect algo come out. Here is my two cents:

  • For the case tg.p <: Period (or maybe narrow down to FixedPeriod ?), treat all the period as Int and we can get a TimeGrid.
  • For the case tg.p <: Float64 (and Irrational included. Although I know π is marked a Irrational type, all calculation on it are approximations.), just fallback to the intersect(::Vector, ::Vector) calculation and output IrregularTimeGrid.

if !assert_intersection_non_empty(tg_fin1, tg_fin2)
return TimeGrid(tg_fin1; n = 0)
end
new_o = max(tg_fin1.o, tg_fin2.o)
last_idx = min(tg_fin1[end], tg_fin2[end])
new_n = tg_fin1.o > tg_fin2.o ? findfirst(isequal(last_idx), tg_fin1) :
findfirst(isequal(last_idx), tg_fin2)
return TimeGrid(new_o, tg_fin1.p, new_n)
end

function assert_intersection_non_empty(tg1::TimeGrid, tg2::TimeGrid)
if (tg1[end] < tg2.o) || (tg2[end] < tg1.o)
return false
end
if tg1.o > tg2.o
idx = findfirst(isequal(tg1.o), tg2)
if isnothing(idx)
return false
end
else
idx = findfirst(isequal(tg2.o), tg1)
if isnothing(idx)
return false
end
end
return true
end

###############################################################################
# Private utils
Expand Down
106 changes: 106 additions & 0 deletions test/timeaxis/timegrid.jl
Original file line number Diff line number Diff line change
Expand Up @@ -418,5 +418,111 @@ end
end
end

@testset "isempty" begin
tg = TimeGrid(DateTime(2021), Hour(1), 1)
@test !isempty(tg)
tg.n = 0
@test isempty(tg)
tg = TimeGrid(DateTime(2021), Hour(1), 0)
@test isempty(tg)
tg = TimeGrid(DateTime(2021), Hour(1))
@test !isempty(tg)
end

@testset "intersect" begin
using TimeSeries
using Test
tg_inf1 = TimeGrid(DateTime(2021, 1, 1, 2), Minute(15))
tg_inf2 = TimeGrid(DateTime(2021, 1, 1, 3), Minute(15))
tg_inf3 = TimeGrid(DateTime(2021, 1, 1, 0), Hour(1))
tg_inf4 = TimeGrid(DateTime(2021, 1, 1, 0, 30), Hour(1))
tg_fin1 = TimeGrid(DateTime(2021, 1, 1, 3), Minute(15), 10)
tg_fin2 = TimeGrid(DateTime(2021, 1, 1, 2), Minute(15), 10)
tg_fin3 = TimeGrid(DateTime(2000, 1, 1, 3), Minute(15), 10)
tg_fin4 = TimeGrid(DateTime(2021, 1, 1, 0), Hour(1), 24)
tg_fin5 = TimeGrid(DateTime(2021, 1, 1, 0, 30), Hour(1), 24)

# infinite with infinite
tg = intersect(tg_inf1, tg_inf2)
@test tg.o == tg_inf2.o
@test !isempty(tg)
@test !isfinite(tg)

tg = intersect(tg_inf3, tg_inf4)
@test isempty(tg)
@test isfinite(tg)

# infinite with finite
tg = intersect(tg_inf1, tg_fin1)
@test tg == tg_fin1
@test !isempty(tg)
@test isfinite(tg)

tg = intersect(tg_fin1, tg_inf1)
@test tg == tg_fin1
@test !isempty(tg)
@test isfinite(tg)

tg = intersect(tg_inf2, tg_fin2)
@test tg.o == tg_inf2.o
@test tg[end] == tg_fin2[end]
@test !isempty(tg)
@test isfinite(tg)

tg = intersect(tg_inf2, tg_fin3)
@test isempty(tg)
@test isfinite(tg)

@test_throws MethodError intersect(tg_inf2, tg_fin5)

tg = intersect(tg_inf3, tg_fin5)
@test isempty(tg)
@test isfinite(tg)

# finite with finite
tg = intersect(tg_fin1, tg_fin2)
@test tg.o == tg_fin1.o
@test tg[end] == tg_fin2[end]
@test !isempty(tg)
@test isfinite(tg)

tg = intersect(tg_fin2, tg_fin1)
@test tg.o == tg_fin1.o
@test tg[end] == tg_fin2[end]
@test !isempty(tg)
@test isfinite(tg)

tg = intersect(tg_fin2, tg_fin3)
@test isempty(tg)
@test isfinite(tg)

tg = intersect(tg_fin3, tg_fin2)
@test isempty(tg)
@test isfinite(tg)

tg = intersect(tg_fin4, tg_fin5)
@test isempty(tg)
@test isfinite(tg)

# multiple time grids
tg = intersect(tg_fin1, tg_fin2, tg_inf1, tg_inf2)
@test tg.o == tg_fin1.o
@test tg[end] == tg_fin2[end]
@test !isempty(tg)
@test isfinite(tg)

tg = intersect(tg_inf1, tg_fin2, tg_fin1, tg_inf2)
@test tg.o == tg_fin1.o
@test tg[end] == tg_fin2[end]
@test !isempty(tg)
@test isfinite(tg)

tg = intersect(tg_inf1, tg_fin2, tg_fin1, tg_inf2, tg_fin3)
@test isempty(tg)
@test isfinite(tg)

@test_throws MethodError intersect(tg_inf1, tg_fin2, tg_fin1, tg_inf2, tg_fin3, tg_fin5)
end


end # @testset "TimeGrid"
Loading