Skip to content

Commit

Permalink
Julia 0.6 broadcast integration (#327)
Browse files Browse the repository at this point in the history
* split broadcast related stuff from apply.jl

* utilities: new helper function - noverlaps

like overlaps, the var args version

* broadcast: julia broadcast 0.6 integration

In Base.Broadcast, broadcast promote the types first via
_containertype and promote_containertype. The dispatch to broadcast_c
with second parameter indicating the promoted type.

* broadcast: fix container type promotion of Array + TimeArray

* utilities: rewrite noverlaps in generated function

* broadcast: wip: rewrite broadcast_c in generated func

* utilities: fix noverlaps

* broadcast: wip: fix broadcast output type

* broadcast: fix column checking

* util: make noverlaps reallocate

* broadcast: use view of timestamp on TimeArray construction

* util: back to overlaps

* broadcast: add test cases
  • Loading branch information
iblislin authored Oct 24, 2017
1 parent ca7a1fc commit 6ddf24e
Show file tree
Hide file tree
Showing 7 changed files with 414 additions and 248 deletions.
1 change: 1 addition & 0 deletions src/TimeSeries.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ include(".timeseriesrc.jl")
include("timearray.jl")
include("split.jl")
include("apply.jl")
include("broadcast.jl")
include("combine.jl")
include("readwrite.jl")
include("utilities.jl")
Expand Down
42 changes: 0 additions & 42 deletions src/apply.jl
Original file line number Diff line number Diff line change
@@ -1,51 +1,9 @@
import Base: +, -
import Base.broadcast
import Base.diff

(+)(ta::TimeArray) = .+ta
(-)(ta::TimeArray) = .-ta

# ND TimeArray <--> MD TimeArray
function broadcast(f, ta1::TimeArray, ta2::TimeArray)
# first test metadata matches
meta = ta1.meta == ta2.meta ? ta1.meta : Void

# determine array widths and name cols accordingly
w1, w2 = length(ta1.colnames), length(ta2.colnames)
if w1 == w2
cnames = [ta1.colnames[i] * "_" * ta2.colnames[i] for i = 1:w1]
elseif w1 == 1
cnames = [ta1.colnames[1] * "_" * ta2.colnames[i] for i = 1:w2]
elseif w2 == 1
cnames = [ta1.colnames[i] * "_" * ta2.colnames[1] for i = 1:w1]
else
error("arrays must have the same number of columns, or one must be a single column")
end

# obtain shared timestamp
idx1, idx2 = overlaps(ta1.timestamp, ta2.timestamp)
tstamp = ta1[idx1].timestamp

# retrieve values that match the Int array matching dates
vals1, vals2 = ta1[idx1].values, ta2[idx2].values

# compute output values
vals = broadcast(f, vals1, vals2)
TimeArray(tstamp, vals, cnames, meta)
end

function broadcast(f, ta::TimeArray, args...)
vals = broadcast(f, ta.values, args...)
TimeArray(ta.timestamp, vals, ta.colnames, ta.meta)
end

# FIXME: How to deal with f(Number, Number, ..., TimeArray)?
function broadcast(f, n::Number, ta::TimeArray, args...)
vals = broadcast(f, n, ta.values, args...)
TimeArray(ta.timestamp, vals, ta.colnames, ta.meta)
end


###### lag, lead ################

function lag(ta::TimeArray{T, N}, n::Int=1;
Expand Down
112 changes: 112 additions & 0 deletions src/broadcast.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import Base.Broadcast:
_containertype, promote_containertype, broadcast_c

# make TimeArray as new resulting container type of Base.Broadcast
_containertype(::Type{<:AbstractTimeSeries}) = TimeArray

# From the default rule of promote_containertype:
# TimeArray, TimeArray -> TimeArray
# And we add following to prevent ambiguous:
# Array, TimeArray -> TimeArray
promote_containertype(::Type{Array}, ::Type{TimeArray}) = TimeArray
promote_containertype(::Type{TimeArray}, ::Type{Array}) = TimeArray

promote_containertype(::Type{Any}, ::Type{TimeArray}) = TimeArray
promote_containertype(::Type{TimeArray}, ::Type{Any}) = TimeArray


@generated function broadcast_c(f, ::Type{TimeArray}, args::Vararg{Any, N}) where {N}
idx = Int[]
colwidth = Expr(:comparison)
overlaps_expr = :(overlaps())

for i in 1:N
if !(args[i] <: TimeArray)
continue
end

# unroll
push!(idx, i)
push!(overlaps_expr.args, :(args[$i].timestamp))

if args[i].parameters[2] == 2 # 2D array
if !isempty(colwidth.args)
push!(colwidth.args, :(==))
end
push!(colwidth.args, :(length(args[$i].colnames)))
end
end

n = length(idx)

# retain meta if all of TimeArray contain the same one
meta_expr = if n == 1
:(args[$(idx[1])].meta)
else
_e = Expr(:comparison, :(args[$(idx[1])].meta))
for i 2:n
push!(_e.args, :(==), :(args[$(idx[i])].meta))
end
:($(_e) ? args[$(idx[1])].meta : Void)
end

# check column length. all of non-single column should have same length
# and contruct new column names
col_check_expr = if length(colwidth.args) > 1 # if we have more than one TimeArray
quote
if !($colwidth)
throw(DimensionMismatch(
"arrays must have the same number of columns, " *
"or one must be a single column"))
end
end
end

col_expr = if n == 1
:(args[$(idx[1])].colnames)
else
_e = :(broadcast(_new_cnames))
append!(_e.args, map(i -> :(args[$i].colnames), idx))
_e
end

# compute output values, broadcast through Array
broadcast_expr = :(broadcast(f))
j = 1
for i 1:N
if args[i] <: TimeArray
if args[i].parameters[2] == 1 # 1D array
push!(broadcast_expr.args, :(view(args[$i].values, tstamp_idx[$j])))
else # 2D array
push!(broadcast_expr.args, :(view(args[$i].values, tstamp_idx[$j], :)))
end
j += 1
else
push!(broadcast_expr.args, :(args[$i]))
end
end

quote
$col_check_expr

# obtain shared timestamp
tstamp_idx = $overlaps_expr

TimeArray(view(args[$(idx[1])].timestamp, tstamp_idx[1]),
$broadcast_expr,
$col_expr,
$meta_expr)
end
end


@generated function _new_cnames(args::Vararg{String, N}) where N
expr = :(string(args[1]))
for i 2:N
push!(expr.args, "_", :(args[$i]))
end
expr
end


#TODO: support broadcast_getindex
39 changes: 24 additions & 15 deletions src/utilities.jl
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
function overlaps(t1::Vector, t2::Vector)
i = j = 1
idx1 = Int[]
idx2 = Int[]
while i <= length(t1) && j <= length(t2)
if t1[i] > t2[j]
j += 1
elseif t1[i] < t2[j]
i += 1
else
push!(idx1, i)
push!(idx2, j)
i += 1
j += 1
overlaps(ts::Vararg{Vector, 1}) = (Base.OneTo(length(ts[1])),)


function overlaps(ts::Vararg{Vector, N}) where {N}
ret = ntuple(_ -> Int[], N)
t1 = ts[1]

for tidx in 2:N
i = j = 1
resize!(ret[1], 0)
t2 = ts[tidx]
while i <= length(t1) && j <= length(t2)
if t1[i] > t2[j]
j += 1
elseif t1[i] < t2[j]
i += 1
else
push!(ret[1], i)
push!(ret[tidx], j)
i += 1
j += 1
end
end
end
(idx1, idx2)
ret
end


function sorted_unique_merge(a::Vector, b::Vector)

i, na, j, nb = 1, length(a), 1, length(b)
Expand Down
Loading

0 comments on commit 6ddf24e

Please sign in to comment.