Skip to content

Commit

Permalink
add a cyclic lookup
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaqz committed Nov 4, 2023
1 parent b20aeb0 commit b75667b
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 6 deletions.
2 changes: 1 addition & 1 deletion src/LookupArrays/LookupArrays.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export AutoStep, AutoBounds, AutoIndex

export LookupArray
export AutoLookup, NoLookup
export Aligned, AbstractSampled, Sampled, AbstractCategorical, Categorical
export Aligned, AbstractSampled, Sampled, AbstractCyclic, Cyclic, AbstractCategorical, Categorical
export Unaligned, Transformed

const StandardIndices = Union{AbstractArray{<:Integer},Colon,Integer,CartesianIndex,CartesianIndices}
Expand Down
77 changes: 75 additions & 2 deletions src/LookupArrays/lookup_arrays.jl
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,7 @@ struct Sampled{T,A<:AbstractVector{T},O,Sp,Sa,M} <: AbstractSampled{T,O,Sp,Sa}
sampling::Sa
metadata::M
end
function Sampled(
data=AutoIndex();
function Sampled(data=AutoIndex();
order=AutoOrder(), span=AutoSpan(),
sampling=AutoSampling(), metadata=NoMetadata()
)
Expand All @@ -298,6 +297,80 @@ function rebuild(l::Sampled;
Sampled(data, order, span, sampling, metadata)
end

abstract type CycleStatus end
struct Cycling <: CycleStatus end
struct NotCycling <: CycleStatus end

abstract type AbstractCyclic{X,T,O,Sp,Sa} <: AbstractSampled{T,O,Sp,Sa} end

cycle(l::AbstractCyclic) = l.cycle
cycle_status(l::AbstractCyclic) = l.cycle_status

no_cycling(l::AbstractCyclic) = rebuild(l; cycle_status=NotCycling())

function cycle_val(l::AbstractCyclic, val)
cycle_start = ordered_first(l)
# This formulation is necessary for dates
ncycles = (val - cycle_start) ÷ (cycle_start + cycle(l) - cycle_start)
res = val - ncycles * cycle(l)
# Catch precision errors
@show val res ncycles cycle(l)
if (cycle_start + (ncycles + 1) * cycle(l)) <= val
@show "higher"
i = 1
while i < 10000
if (cycle_start + (ncycles + i) * cycle(l)) > val
res = val - (ncycles + i - 1) * cycle(l)
@show res i
return res
end
i += 1
end
elseif res < cycle_start
@show "lower"
i = 1
while i < 10000
res = val - (ncycles - i + 1) * cycle(l)
if res >= cycle_start
res = val - (ncycles - i + 1) * cycle(l)
@show res i val
return res
end
i += 1
end
else
@show "no change"
return res
end
error("`Cyclic` lookup too innacurate, value not found")
end


struct Cyclic{X,T,A<:AbstractVector{T},O,Sp,Sa,M,C} <: AbstractCyclic{X,T,O,Sp,Sa}
data::A
order::O
span::Sp
sampling::Sa
metadata::M
cycle::C
cycle_status::X
end
function Cyclic(data=AutoIndex();
order=AutoOrder(), span=AutoSpan(),
sampling=AutoSampling(), metadata=NoMetadata(),
cycle, # Mandatory keyword, there are too many possible bugs with auto detection
)
cycle_status = Cycling() # Not use-facing
Cyclic(data, order, span, sampling, metadata, cycle, cycle_status)
end

function rebuild(l::Cyclic;
data=parent(l), order=order(l), span=span(l), sampling=sampling(l), metadata=metadata(l),
cycle=cycle(l), _cycle_status=cycle_status(l), kw...
)
Cyclic(data, order, span, sampling, metadata, cycle, cycle_status)
end

"""
AbstractCategorical <: Aligned
Expand Down
1 change: 0 additions & 1 deletion src/LookupArrays/lookup_traits.jl
Original file line number Diff line number Diff line change
Expand Up @@ -269,4 +269,3 @@ change the `LookupArray` type without changing the index values.
struct AutoIndex <: AbstractVector{Int} end

Base.size(::AutoIndex) = (0,)

17 changes: 17 additions & 0 deletions src/LookupArrays/selector.jl
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ selectindices(l::LookupArray, sel::At{<:AbstractVector}) = _selectvec(l, sel)

_selectvec(l, sel) = [selectindices(l, rebuild(sel; val=v)) for v in val(sel)]

function at(lookup::AbstractCyclic{Cycling}, sel::At; kw...)
cycled_sel = rebuild(sel; val=cycle_val(lookup, val(sel)))
return at(no_cycling(lookup), cycled_sel; kw...)
end
function at(lookup::NoLookup, sel::At; kw...)
v = val(sel)
r = round(Int, v)
Expand Down Expand Up @@ -226,6 +230,10 @@ end
selectindices(l::LookupArray, sel::Near) = near(l, sel)
selectindices(l::LookupArray, sel::Near{<:AbstractVector}) = _selectvec(l, sel)

function near(lookup::AbstractCyclic{Cycling}, sel::Near)
cycled_sel = rebuild(sel; val=cycle_val(lookup, val(sel)))
near(no_cycling(lookup), cycled_sel)
end
near(lookup::NoLookup, sel::Near{<:Real}) = max(1, min(round(Int, val(sel)), lastindex(lookup)))
function near(lookup::LookupArray, sel::Near)
span(lookup) isa Union{Irregular,Explicit} && locus(lookup) isa Union{Start,End} &&
Expand Down Expand Up @@ -306,6 +314,10 @@ end
selectindices(l::LookupArray, sel::Contains; kw...) = contains(l, sel)
selectindices(l::LookupArray, sel::Contains{<:AbstractVector}) = _selectvec(l, sel)

function contains(lookup::AbstractCyclic{Cycling}, sel::Contains; kw...)
cycled_sel = rebuild(sel; val=cycle_val(lookup, val(sel)))
return contains(no_cycling(lookup), cycled_sel; kw...)
end
function contains(l::NoLookup, sel::Contains; kw...)
i = Int(val(sel))
i in l || throw(SelectorError(l, i))
Expand Down Expand Up @@ -484,6 +496,11 @@ function between(l::NoLookup, sel::Interval)
x = intersect(sel, first(axes(l, 1))..last(axes(l, 1)))
return ceil(Int, x.left):floor(Int, x.right)
end
# function between(l::AbstractCyclic{Cycling}, sel::Interval)
# cycle_val(l, sel.x)..cycle_val(l, sel.x)
# cycled_sel = rebuild(sel; val=)
# near(no_cycling(lookup), cycled_sel; kw...)
# end
between(l::LookupArray, interval::Interval) = between(sampling(l), l, interval)
# This is the main method called above
function between(sampling::Sampling, l::LookupArray, interval::Interval)
Expand Down
58 changes: 56 additions & 2 deletions test/selector.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using DimensionalData, Test, Unitful, Combinatorics, Dates, IntervalSets, Extents
using DimensionalData.LookupArrays, DimensionalData.Dimensions
using .LookupArrays: between, touches, at, near, contains, bounds, SelectorError
using .LookupArrays: between, touches, at, near, contains, bounds, SelectorError, cycle_val

a = [1 2 3 4
5 6 7 8
Expand Down Expand Up @@ -1326,7 +1326,61 @@ end

end

@testset "NoIndex" begin
@testset "Cyclic lookup" begin
lookups = (
day=Cyclic(DateTime(2001):Day(1):DateTime(2002, 12, 31); cycle=Year(1), order=ForwardOrdered(), span=Regular(Day(1)), sampling=Intervals(Start())),
week=Cyclic(DateTime(2001):Week(1):DateTime(2002, 12, 31); cycle=Year(1), order=ForwardOrdered(), span=Regular(Week(1)), sampling=Intervals(Start())),
month=Cyclic(DateTime(2001):Month(1):DateTime(2002, 12, 31); cycle=Year(1), order=ForwardOrdered(), span=Regular(Month(1)), sampling=Intervals(Start())),
month_month=Cyclic(DateTime(2001):Month(1):DateTime(2002, 1, 31); cycle=Month(1), order=ForwardOrdered(), span=Regular(Month(1)), sampling=Intervals(Start())),
)
lookup = lookups[1]

for lookup in lookups
# Test exact cycles
@test at(lookup, At(DateTime(1))) == 1
@test at(lookup, At(DateTime(1999))) == 1
@test at(lookup, At(DateTime(2000))) == 1
@test at(lookup, At(DateTime(2001))) == 1
@test at(lookup, At(DateTime(4000))) == 1
@test near(lookup, Near(DateTime(1))) == 1
@test near(lookup, Near(DateTime(1999))) == 1
@test near(lookup, Near(DateTime(2000))) == 1
@test near(lookup, Near(DateTime(2001))) == 1
@test near(lookup, Near(DateTime(4000))) == 1
@test contains(lookup, Contains(DateTime(1))) == 1
@test contains(lookup, Contains(DateTime(1999))) == 1
@test contains(lookup, Contains(DateTime(2000))) == 1
@test contains(lookup, Contains(DateTime(2001))) == 1
@test contains(lookup, Contains(DateTime(4000))) == 1
end

lookup = lookups.month
@test at(lookup, At(DateTime(1, 12))) == 12
@test at(lookup, At(DateTime(1999, 12))) == 12
@test at(lookup, At(DateTime(2000, 12))) == 12
@test at(lookup, At(DateTime(2001, 12))) == 12
@test at(lookup, At(DateTime(3000, 12))) == 12
lookup = lookups.day
@test at(lookup, At(DateTime(1, 12, 31))) == 365
@test at(lookup, At(DateTime(1999, 12, 31))) == 365
# This is kinda wrong, as there are 366 days in 2000
# But our lookup has 365. Leap years would be handled
# properly with a four year cycle
@test at(lookup, At(DateTime(2000, 12, 31))) == 365
@test at(lookup, At(DateTime(2001, 12, 31))) == 365
@test at(lookup, At(DateTime(3000, 12, 31))) == 365

@testset "Leap years are correct with four year cycles" begin
lookup = Cyclic(DateTime(2000):Day(1):DateTime(2003, 12, 31); cycle=Year(4), order=ForwardOrdered(), span=Regular(Day(1)), sampling=Intervals(Start()))
@test at(lookup, At(DateTime(1, 12, 31))) == findfirst(==(DateTime(2001, 12, 31)), lookup)
@test at(lookup, At(DateTime(1999, 12, 31))) == findfirst(==(DateTime(1999 + 4, 12, 31)), lookup)
@test at(lookup, At(DateTime(2000, 12, 31))) == 366 == findfirst(==(DateTime(2000, 12, 31)), lookup)
@test at(lookup, At(DateTime(2007, 12, 31))) == findfirst(==(DateTime(2007 - 4, 12, 31)), lookup)
@test at(lookup, At(DateTime(3000, 12, 31))) == 366 == findfirst(==(DateTime(3000 - 250 * 4, 12, 31)), lookup)
end
end

@testset "NoLookup" begin
l = NoLookup(1:100)
@test_throws SelectorError selectindices(l, At(0))
@test_throws SelectorError selectindices(l, At(200))
Expand Down

0 comments on commit b75667b

Please sign in to comment.