From 1ce019b6c61124bddbd3cdb96778ccb9a822d81f Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Wed, 15 Sep 2021 13:15:44 -0700 Subject: [PATCH 01/59] factorize interpretation --- src/root.jl | 116 ++++++++++++++++++++++++++-------------------------- 1 file changed, 59 insertions(+), 57 deletions(-) diff --git a/src/root.jl b/src/root.jl index a20a6c52..1335e04a 100644 --- a/src/root.jl +++ b/src/root.jl @@ -197,66 +197,68 @@ function interped_data(rawdata, rawoffsets, ::Type{Bool}, ::Type{Nojagg}) # specialized case to get Vector{Bool} instead of BitVector return map(ntoh,reinterpret(Bool, rawdata)) end -function interped_data(rawdata, rawoffsets, ::Type{T}, ::Type{J}) where {T, J<:JaggType} - # there are two possibility, one is the leaf is just normal leaf but the title has "[...]" in it - # magic offsets, seems to be common for a lot of types, see auto.py in uproot3 - # only needs when the jaggedness comes from TLeafElements, not needed when - # the jaggedness comes from having "[]" in TLeaf's title - # the other is where we need to auto detector T bsaed on class name - # we want the fundamental type as `reinterpret` will create vector - if J === Nojagg - return ntoh.(reinterpret(T, rawdata)) - elseif J === Offsetjaggjagg # the branch is doubly jagged - jagg_offset = 10 - subT = eltype(eltype(T)) - out = VectorOfVectors(T(), Int32[1]) - @views for i in 1:(length(rawoffsets)-1) - flat = rawdata[(rawoffsets[i]+1+jagg_offset:rawoffsets[i+1])] - row = VectorOfVectors{subT}() - cursor = 1 - while cursor < length(flat) - n = ntoh(reinterpret(Int32, flat[cursor:cursor+sizeof(Int32)-1])[1]) - cursor += sizeof(Int32) - b = ntoh.(reinterpret(subT, flat[cursor:cursor+n*sizeof(subT)-1])) - cursor += n*sizeof(subT) - push!(row, b) - end - push!(out, row) - end - return out - else # the branch is singly jagged - # for each "event", the index range is `offsets[i] + jagg_offset + 1` to `offsets[i+1]` - # this is why we need to append `rawoffsets` in the `readbranchraw()` call - # when you use this range to index `rawdata`, you will get raw bytes belong to each event - # Say your real data is Int32 and you see 8 bytes after indexing, then this event has [num1, num2] as real data - _size = sizeof(eltype(T)) - if J === Offsetjagg - jagg_offset = 10 - dp = 0 # book keeping for copy_to! - lr = length(rawoffsets) - offset = Vector{Int32}(undef, lr) - offset[1] = 0 - @views @inbounds for i in 1:lr-1 - start = rawoffsets[i]+jagg_offset+1 - stop = rawoffsets[i+1] - l = stop-start+1 - if l > 0 - unsafe_copyto!(rawdata, dp+1, rawdata, start, l) - dp += l - offset[i+1] = offset[i] + l - else - # when we have an empty [] in jagged basket - offset[i+1] = offset[i] - end - end - resize!(rawdata, dp) +function interped_data(rawdata, rawoffsets, ::Type{T}, ::Type{Nojagg}) where T + return ntoh.(reinterpret(T, rawdata)) +end +# there are two possibility, one is the leaf is just normal leaf but the title has "[...]" in it +# magic offsets, seems to be common for a lot of types, see auto.py in uproot3 +# only needs when the jaggedness comes from TLeafElements, not needed when +# the jaggedness comes from having "[]" in TLeaf's title +# the other is where we need to auto detector T bsaed on class name +# we want the fundamental type as `reinterpret` will create vector +function interped_data(rawdata, rawoffsets, ::Type{T}, ::Type{Nooffsetjagg}) where T + _size = sizeof(eltype(T)) + real_data = ntoh.(reinterpret(T, rawdata)) + rawoffsets .= (rawoffsets .÷ _size) .+ 1 + return VectorOfVectors(real_data, rawoffsets, ArraysOfArrays.no_consistency_checks) +end +function interped_data(rawdata, rawoffsets, ::Type{T}, ::Type{Offsetjagg}) where T + # for each "event", the index range is `offsets[i] + jagg_offset + 1` to `offsets[i+1]` + # this is why we need to append `rawoffsets` in the `readbranchraw()` call + # when you use this range to index `rawdata`, you will get raw bytes belong to each event + # Say your real data is Int32 and you see 8 bytes after indexing, then this event has [num1, num2] as real data + _size = sizeof(eltype(T)) + jagg_offset = 10 + dp = 0 # book keeping for copy_to! + lr = length(rawoffsets) + offset = Vector{Int32}(undef, lr) + offset[1] = 0 + @views @inbounds for i in 1:lr-1 + start = rawoffsets[i]+jagg_offset+1 + stop = rawoffsets[i+1] + l = stop-start+1 + if l > 0 + unsafe_copyto!(rawdata, dp+1, rawdata, start, l) + dp += l + offset[i+1] = offset[i] + l else - offset = rawoffsets + # when we have an empty [] in jagged basket + offset[i+1] = offset[i] + end + end + resize!(rawdata, dp) + real_data = ntoh.(reinterpret(T, rawdata)) + offset .= (offset .÷ _size) .+ 1 + return VectorOfVectors(real_data, offset, ArraysOfArrays.no_consistency_checks) +end +function interped_data(rawdata, rawoffsets, ::Type{T}, ::Type{Offsetjaggjagg}) where T + jagg_offset = 10 + subT = eltype(eltype(T)) + out = VectorOfVectors(T(), Int32[1]) + @views for i in 1:(length(rawoffsets)-1) + flat = rawdata[(rawoffsets[i]+1+jagg_offset:rawoffsets[i+1])] + row = VectorOfVectors{subT}() + cursor = 1 + while cursor < length(flat) + n = ntoh(reinterpret(Int32, flat[cursor:cursor+sizeof(Int32)-1])[1]) + cursor += sizeof(Int32) + b = ntoh.(reinterpret(subT, flat[cursor:cursor+n*sizeof(subT)-1])) + cursor += n*sizeof(subT) + push!(row, b) end - real_data = ntoh.(reinterpret(T, rawdata)) - offset .= (offset .÷ _size) .+ 1 - return VectorOfVectors(real_data, offset, ArraysOfArrays.no_consistency_checks) + push!(out, row) end + return out end function _normalize_ftype(fType) From fadd2aa456a114df68f313c9566c17cdcb1dcc27 Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Wed, 15 Sep 2021 13:15:55 -0700 Subject: [PATCH 02/59] make methods more specific --- src/custom.jl | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/custom.jl b/src/custom.jl index 1425bcd0..7d1c6490 100644 --- a/src/custom.jl +++ b/src/custom.jl @@ -73,7 +73,7 @@ function interped_data(rawdata, rawoffsets, ::Type{Vector{LVF64}}, ::Type{Offset offset .+= 1 VectorOfVectors(real_data, offset) end -function interped_data(rawdata, rawoffsets, ::Type{LVF64}, ::Type{J}) where {T, J <: JaggType} +function interped_data(rawdata, rawoffsets, ::Type{LVF64}, ::Type{Nojagg}) # even with rawoffsets, we know each TLV is destinied to be 64 bytes [ reinterpret(LVF64, x) for x in Base.Iterators.partition(rawdata, 64) @@ -91,7 +91,10 @@ end function readtype(io::IO, T::Type{_KM3NETDAQHit}) T(readtype(io, Int32), read(io, UInt8), read(io, Int32), read(io, UInt8)) end -function interped_data(rawdata, rawoffsets, ::Type{Vector{_KM3NETDAQHit}}, ::Type{J}) where {T, J <: UnROOT.JaggType} +function interped_data(rawdata, rawoffsets, ::Type{Vector{_KM3NETDAQHit}}, ::Type{Nojagg}) + UnROOT.splitup(rawdata, rawoffsets, _KM3NETDAQHit, skipbytes=10) +end +function interped_data(rawdata, rawoffsets, ::Type{Vector{_KM3NETDAQHit}}, ::Type{Offsetjagg}) UnROOT.splitup(rawdata, rawoffsets, _KM3NETDAQHit, skipbytes=10) end @@ -114,7 +117,10 @@ function readtype(io::IO, T::Type{_KM3NETDAQTriggeredHit}) T(dom_id, channel_id, tdc, tot, trigger_mask) end -function UnROOT.interped_data(rawdata, rawoffsets, ::Type{Vector{_KM3NETDAQTriggeredHit}}, ::Type{J}) where {T, J <: UnROOT.JaggType} +function UnROOT.interped_data(rawdata, rawoffsets, ::Type{Vector{_KM3NETDAQTriggeredHit}}, ::Type{Nojagg}) + UnROOT.splitup(rawdata, rawoffsets, _KM3NETDAQTriggeredHit, skipbytes=10) +end +function UnROOT.interped_data(rawdata, rawoffsets, ::Type{Vector{_KM3NETDAQTriggeredHit}}, ::Type{Offsetjagg}) UnROOT.splitup(rawdata, rawoffsets, _KM3NETDAQTriggeredHit, skipbytes=10) end @@ -146,6 +152,6 @@ function readtype(io::IO, T::Type{_KM3NETDAQEventHeader}) T(detector_id, run, frame_index, UTC_seconds, UTC_16nanosecondcycles, trigger_counter, trigger_mask, overlays) end -function UnROOT.interped_data(rawdata, rawoffsets, ::Type{_KM3NETDAQEventHeader}, ::Type{J}) where {T, J <: UnROOT.JaggType} +function UnROOT.interped_data(rawdata, rawoffsets, ::Type{_KM3NETDAQEventHeader}, ::Type{Nojagg}) UnROOT.splitup(rawdata, rawoffsets, _KM3NETDAQEventHeader, jagged=false) end From e608734e00bc00f09e53d806e1de04a5fc417e7c Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Wed, 15 Sep 2021 13:24:03 -0700 Subject: [PATCH 03/59] for ci --- test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index a7438143..5527f521 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -329,7 +329,7 @@ end @testset "Branch filtering" begin # Branch selection behavior: if not regex, require exact name match treebranches = ["Muon_pt", "Muon_eta", "Muon_phi", "Muon_charge", "Muon_ptErr", - "Muon_", "_pt", "Muon.pt"] + "Muon_", "_pt", "Muon.pt"] _m(s::AbstractString) = isequal(s) _m(r::Regex) = Base.Fix1(occursin, r) filter_branches(selected) = Set(mapreduce(b->filter(_m(b), treebranches), ∪, selected)) From e17bd8314556cf2b235c33c15c225379812deb0c Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Thu, 16 Sep 2021 01:08:15 -0700 Subject: [PATCH 04/59] TDirectory support (#107) * try1 * recursive and unit tests * add files * actually add tests --- src/bootstrap.jl | 14 +++++++++ src/displays.jl | 18 +++++++---- src/root.jl | 18 ++++++++++- test/runtests.jl | 12 ++++++++ test/samples/issue11_tdirectory.c | 43 ++++++++++++++++++++++++++ test/samples/issue11_tdirectory.root | Bin 0 -> 15274 bytes test/samples/tdir_complicated.py | 44 +++++++++++++++++++++++++++ test/samples/tdir_complicated.root | Bin 0 -> 9850 bytes 8 files changed, 142 insertions(+), 7 deletions(-) create mode 100644 test/samples/issue11_tdirectory.c create mode 100644 test/samples/issue11_tdirectory.root create mode 100644 test/samples/tdir_complicated.py create mode 100644 test/samples/tdir_complicated.root diff --git a/src/bootstrap.jl b/src/bootstrap.jl index 2cfaa6b8..4afbda67 100644 --- a/src/bootstrap.jl +++ b/src/bootstrap.jl @@ -827,6 +827,20 @@ function TH(io, tkey::TKey, refs) fields end +function TDirectory(io, tkey::TKey, refs) + fobj = io + seekstart(fobj, tkey) + + # almost verbatim from L95 to L101 of root.jl + dir_header = unpack(fobj, ROOTDirectoryHeader) + seek(fobj, dir_header.fSeekKeys) + header_key = unpack(fobj, TKey) + n_keys = readtype(fobj, Int32) + keys = [unpack(fobj, TKey) for _ in 1:n_keys] + + directory = ROOTDirectory(header_key.fName, dir_header, keys, fobj, refs) + return directory +end # FIXME idk what is going on but this just looks like a TTree..... function TNtuple(io, tkey::TKey, refs) diff --git a/src/displays.jl b/src/displays.jl index e410cbe7..f8f42228 100644 --- a/src/displays.jl +++ b/src/displays.jl @@ -7,14 +7,14 @@ struct TKeyNode name::AbstractString classname::AbstractString end -function children(f::ROOTFile) +function children(f::T) where T <: Union{ROOTFile,ROOTDirectory} # display TTrees recursively # subsequent TTrees with duplicate fName will be skipped # since TKey cycle number is guaranteed to be decreasing # then all TKeys in the file which are not for a TTree seen = Set{String}() - ch = Vector{Union{TTree,TKeyNode}}() - lock(f) + ch = Vector{Union{TTree,TKeyNode,ROOTDirectory}}() + T === ROOTFile ? lock(f) : nothing for k in keys(f) try obj = f[k] @@ -25,12 +25,17 @@ function children(f::ROOTFile) catch end end - for tkey in f.directory.keys + tkeys = T === ROOTFile ? f.directory.keys : f.keys + for tkey in tkeys kn = TKeyNode(tkey.fName, tkey.fClassName) kn.classname == "TTree" && continue - push!(ch, kn) + if kn.classname == "TDirectory" + push!(ch, f[tkey.fName]) + else + push!(ch, kn) + end end - unlock(f) + T === ROOTFile ? unlock(f) : nothing ch end function children(t::TTree) @@ -45,6 +50,7 @@ function children(t::TTree) end printnode(io::IO, t::TTree) = print(io, "$(t.fName) (TTree)") printnode(io::IO, f::ROOTFile) = print(io, f.filename) +printnode(io::IO, f::ROOTDirectory) = print(io, "$(f.name) (TDirectory)") printnode(io::IO, k::TKeyNode) = print(io, "$(k.name) ($(k.classname))") function Base.show(io::IO, tree::LazyTree) diff --git a/src/root.jl b/src/root.jl index 1335e04a..a7518673 100644 --- a/src/root.jl +++ b/src/root.jl @@ -2,6 +2,8 @@ struct ROOTDirectory name::AbstractString header::ROOTDirectoryHeader keys::Vector{TKey} + fobj::IOStream + refs::Dict{Int32, Any} end struct ROOTFile @@ -92,7 +94,7 @@ function ROOTFile(filename::AbstractString; customstructs = Dict("TLorentzVector n_keys = readtype(fobj, Int32) keys = [unpack(fobj, TKey) for _ in 1:n_keys] - directory = ROOTDirectory(tkey.fName, dir_header, keys) + directory = ROOTDirectory(tkey.fName, dir_header, keys, fobj, streamers.refs) ROOTFile(filename, format_version, header, fobj, tkey, streamers, directory, customstructs, ReentrantLock()) end @@ -154,6 +156,20 @@ end end end +# FIXME unify with above? +@memoize LRU(maxsize = 2000) function getindex(d::ROOTDirectory, s) +# function getindex(d::ROOTDirectory, s) + if '/' ∈ s + @debug "Splitting path '$s' and getting items recursively" + paths = split(s, '/') + return d[first(paths)][join(paths[2:end], "/")] + end + tkey = d.keys[findfirst(isequal(s), keys(d))] + streamer = getfield(@__MODULE__, Symbol(tkey.fClassName)) + S = streamer(d.fobj, tkey, d.refs) + return S +end + function Base.keys(f::ROOTFile) keys(f.directory) end diff --git a/test/runtests.jl b/test/runtests.jl index 5527f521..43faac90 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -656,3 +656,15 @@ end end end end + +@testset "TDirectory" begin + f = UnROOT.samplefile("tdir_complicated.root") + @test length(keys(f["mydir"])) == 4 + @test sort(keys(f["mydir"])) == ["Events", "c", "d", "mysubdir"] + @test sort(keys(f["mydir/mysubdir"])) == ["e", "f"] + @test sum(length.(LazyTree(f, "mydir/Events").Jet_pt)) == 4 + @test sum(length.(f["mydir/Events/Jet_pt"])) == 4 + + f = UnROOT.samplefile("issue11_tdirectory.root") + @test sum(f["Data/mytree/Particle0_E"]) ≈ 1012.0 +end diff --git a/test/samples/issue11_tdirectory.c b/test/samples/issue11_tdirectory.c new file mode 100644 index 00000000..4d5d2566 --- /dev/null +++ b/test/samples/issue11_tdirectory.c @@ -0,0 +1,43 @@ +#include "TString.h" +#include "TFile.h" +#include "TTree.h" + +#include +#include "stdlib.h" + +int issue11_tdirectory() { + + TFile *f = new TFile("issue11_tdirectory.root", "recreate"); + f->mkdir("Data"); + f->cd("Data"); + TTree *t = new TTree("mytree", "mytree"); + + auto app = "EXYZ"; + const uint Np = 10; + + // + double v[Np][4]; + for (uint n=0; nBranch(TString::Format("Particle%d_%c", n, app[i]), &v[n][i]); + } + } + + const int Nev = 23; + double inc = 0.0; + + for (uint i=0; iFill(); + } + + t->Write(); + f->Close(); + + return 0; +} diff --git a/test/samples/issue11_tdirectory.root b/test/samples/issue11_tdirectory.root new file mode 100644 index 0000000000000000000000000000000000000000..963133add7dfb9da544a023773d75da34d8d0a7e GIT binary patch literal 15274 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%0DMucz$-}r_f5O6WVx*4bo3( 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 Hk^%n*l;Awl literal 0 HcmV?d00001 From ef584bcd17c738561b5a8e386e7c53a374efee98 Mon Sep 17 00:00:00 2001 From: Tamas Gal Date: Thu, 16 Sep 2021 10:11:49 +0200 Subject: [PATCH 05/59] Bump version number --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 531b8232..1ea2ab5b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnROOT" uuid = "3cd96dde-e98d-4713-81e9-a4a1b0235ce9" authors = ["Tamas Gal", "Jerry Ling", "Johannes Schumann", "Nick Amin"] -version = "0.6.0" +version = "0.6.1" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 0939b2762fc62e7b0b5dafc80e1719f304469607 Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Thu, 16 Sep 2021 16:06:29 -0700 Subject: [PATCH 06/59] fix --- src/root.jl | 4 ++++ test/runtests.jl | 8 ++++++++ test/samples/issue108_small.root | Bin 0 -> 169905 bytes 3 files changed, 12 insertions(+) create mode 100644 test/samples/issue108_small.root diff --git a/src/root.jl b/src/root.jl index a7518673..3d38ded2 100644 --- a/src/root.jl +++ b/src/root.jl @@ -367,6 +367,10 @@ function auto_T_JaggT(f::ROOTFile, branch; customstructs::Dict{String, Type}) UInt32 elseif elname == "unsigned char" Char + elseif elname == "unsigned short" + UInt16 + elseif elname == "ulong64" + UInt64 else _type = getfield(Base, Symbol(:C, elname)) end diff --git a/test/runtests.jl b/test/runtests.jl index 43faac90..36e682fb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -536,6 +536,14 @@ end _ = length.(arr); @test length.(arr.buffer) == length.(arr.buffer_range) close(rootfile) + + # issue 108 + # unsigned short -> Int16, ulong64 -> UInt64 + # file minified with `rooteventselector --recreate -l 2 "trackntuple.root:trackingNtuple/tree" issue108_small.root` + rootfile = ROOTFile(joinpath(SAMPLES_DIR, "issue108_small.root")) + @test rootfile["tree/trk_algoMask"][2] == [0x0000000000004000, 0x0000000000004000, 0x0000000000004000, 0x0000000000004000] + @test rootfile["tree/pix_ladder"][3][1:5] == UInt16[0x0001, 0x0001, 0x0001, 0x0001, 0x0003] + close(rootfile) end @testset "jagged subbranch type by leaf" begin diff --git a/test/samples/issue108_small.root b/test/samples/issue108_small.root new file mode 100644 index 0000000000000000000000000000000000000000..b8f704801bb2e45569b0de391d228cfa9f594dac GIT binary patch literal 169905 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 literal 0 HcmV?d00001 From a90904f8cedda0aca8dcd9b5e4880b9f5e4d2e1e Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Thu, 16 Sep 2021 16:11:31 -0700 Subject: [PATCH 07/59] [skip ci] bump version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 1ea2ab5b..cd813eba 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnROOT" uuid = "3cd96dde-e98d-4713-81e9-a4a1b0235ce9" authors = ["Tamas Gal", "Jerry Ling", "Johannes Schumann", "Nick Amin"] -version = "0.6.1" +version = "0.6.2" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 2a9e4ca20a0fd41873a18737f6a429b28c7acb2c Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Thu, 16 Sep 2021 18:34:01 -0700 Subject: [PATCH 08/59] add more basic types (#110) --- src/iteration.jl | 1 + src/root.jl | 14 +++++--- test/runtests.jl | 6 ++++ test/samples/tree_basictypes.py | 57 ++++++++++++++++++++++++++++++ test/samples/tree_basictypes.root | Bin 0 -> 9106 bytes 5 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 test/samples/tree_basictypes.py create mode 100644 test/samples/tree_basictypes.root diff --git a/src/iteration.jl b/src/iteration.jl index 1cfee31b..776c51a8 100644 --- a/src/iteration.jl +++ b/src/iteration.jl @@ -97,6 +97,7 @@ mutable struct LazyBranch{T,J,B} <: AbstractVector{T} function LazyBranch(f::ROOTFile, b::Union{TBranch,TBranchElement}) T, J = auto_T_JaggT(f, b; customstructs=f.customstructs) + T = (T === Vector{Bool} ? BitVector : T) _buffer = J === Nojagg ? T[] : VectorOfVectors(T(), Int32[1]) return new{T,J,typeof(_buffer)}(f, b, length(b), b.fBasketEntry, diff --git a/src/root.jl b/src/root.jl index 3d38ded2..0969072a 100644 --- a/src/root.jl +++ b/src/root.jl @@ -127,11 +127,11 @@ end function Base.getindex(f::ROOTFile, s::AbstractString) S = _getindex(f, s) if S isa Union{TBranch, TBranchElement} - try # if we can't construct LazyBranch, just give up (maybe due to custom class) + # try # if we can't construct LazyBranch, just give up (maybe due to custom class) return LazyBranch(f, S) - catch - @warn "Can't automatically create LazyBranch for branch $s. Returning a branch object" - end + # catch + # @warn "Can't automatically create LazyBranch for branch $s. Returning a branch object" + # end end S end @@ -366,9 +366,13 @@ function auto_T_JaggT(f::ROOTFile, branch; customstructs::Dict{String, Type}) elseif elname == "unsigned int" UInt32 elseif elname == "unsigned char" - Char + UInt8 elseif elname == "unsigned short" UInt16 + elseif elname == "unsigned long" + UInt64 + elseif elname == "long64" + Int64 elseif elname == "ulong64" UInt64 else diff --git a/test/runtests.jl b/test/runtests.jl index 36e682fb..b5ffb36b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -676,3 +676,9 @@ end f = UnROOT.samplefile("issue11_tdirectory.root") @test sum(f["Data/mytree/Particle0_E"]) ≈ 1012.0 end + +@testset "Basic C++ types" begin + f = UnROOT.samplefile("tree_basictypes.root") + onesrow = LazyTree(f,"t")[2] |> values .|> first .|> Int + @test all(onesrow .== 1) +end diff --git a/test/samples/tree_basictypes.py b/test/samples/tree_basictypes.py new file mode 100644 index 00000000..5340be9b --- /dev/null +++ b/test/samples/tree_basictypes.py @@ -0,0 +1,57 @@ +import ROOT as r + +# from https://en.cppreference.com/w/cpp/language/types +typenames = [ + # char + "char", "unsigned char", + # bool + "bool", + # short + "short", "short int", "signed short", "signed short int", "unsigned short", "unsigned short int", + # int + "int", "signed", "signed int", "unsigned", "unsigned int", + # long + "long", "long int", "long int", "signed long", "signed long int", "unsigned long", "unsigned long int", + # long long + "long long", "long long int", "signed long long", "signed long long int", "unsigned long long", "unsigned long long int", + # float/double + "float", "double", + ] + +print(len(typenames), typenames) + +roottypestrs = set() +for typename in typenames: + obj = r.vector(typename) + # print(typename, obj, obj()) + roottypestr = str(obj).split("'")[1].split("<")[1].split(">")[0] + roottypestrs.add(roottypestr) + +# since many of the above map to the same "root type", `roottypestrs` ends up being + +# {'long', 'Long64_t', 'unsigned char', 'short', 'unsigned long', 'long double', +# 'char', 'unsigned short', 'ULong64_t', 'float', 'bool', 'double', +# 'int', 'unsigned int'} + +print(len(roottypestrs), roottypestrs) + +import ROOT as r +f = r.TFile("tree_basictypes.root", "recreate") +t = r.TTree("t", "") + +d_objs = dict() +for typename in roottypestrs: + nicename = typename.lower().replace("_t","").replace(" ","") + obj = r.vector(typename)() + t.Branch(nicename, obj) + d_objs[typename] = obj + +for ievt in range(3): + for typename, obj in d_objs.items(): + obj.clear() + for _ in range(ievt): + obj.push_back(1) + t.Fill() + +t.Write() +f.Close() diff --git a/test/samples/tree_basictypes.root b/test/samples/tree_basictypes.root new file mode 100644 index 0000000000000000000000000000000000000000..ba0da600c5be4461bc48a48dc0cbd21b30ea7ce8 GIT binary patch literal 9106 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 Date: Fri, 17 Sep 2021 14:16:22 -0700 Subject: [PATCH 09/59] iterate on baskets + an optimization (#111) * iterate on baskets * 10-50% speedup for simple benchmarks * simplify --- src/bootstrap.jl | 1 + src/iteration.jl | 41 ++++++++++++++++++++++++++++++----------- test/runtests.jl | 8 ++++++++ 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/bootstrap.jl b/src/bootstrap.jl index 4afbda67..8768bb34 100644 --- a/src/bootstrap.jl +++ b/src/bootstrap.jl @@ -418,6 +418,7 @@ function Base.hash(b::Union{TBranch, TBranchElement}, h::UInt) end Base.length(b::Union{TBranch, TBranchElement}) = b.fEntries Base.eachindex(b::Union{TBranch, TBranchElement}) = Base.OneTo(b.fEntries) +numbaskets(b::Union{TBranch, TBranchElement}) = findfirst(x->x>(b.fEntries-1),b.fBasketEntry)-1 function Base.eltype(b::Union{TBranch, TBranchElement}) T, jagT = interp_jaggT(b) jagT === Nojagg ? T : Vector{T} diff --git a/src/iteration.jl b/src/iteration.jl index 776c51a8..0ff09b13 100644 --- a/src/iteration.jl +++ b/src/iteration.jl @@ -38,6 +38,7 @@ end """ basketarray(f::ROOTFile, path::AbstractString, ith) basketarray(f::ROOTFile, branch::Union{TBranch, TBranchElement}, ith) + basketarray(lb::LazyBranch, ith) Reads actual data from ith basket of a branch. This function first calls [`readbasket`](@ref) to obtain raw bytes and offsets of a basket, then calls [`auto_T_JaggT`](@ref) followed @@ -57,6 +58,16 @@ function basketarray(f::ROOTFile, branch, ithbasket) return interped_data(rawdata, rawoffsets, T, J) end +""" + basketarray_iter(f::ROOTFile, branch::Union{TBranch, TBranchElement}) + basketarray_iter(lb::LazyBranch) + +Returns a `Base.Generator` yielding the output of `basketarray()` for all baskets. +""" +function basketarray_iter(f::ROOTFile, branch) + return (basketarray(f, branch, i) for i in 1:numbaskets(branch)) +end + # function barrior to make getting individual index faster # TODO upstream some types into parametric types for Branch/BranchElement """ @@ -121,6 +132,9 @@ Base.firstindex(ba::LazyBranch) = 1 Base.lastindex(ba::LazyBranch) = ba.L Base.eltype(ba::LazyBranch{T,J,B}) where {T,J,B} = T +basketarray(lb::LazyBranch, ithbasket) = basketarray(lb.f, lb.b, ithbasket) +basketarray_iter(lb::LazyBranch) = basketarray_iter(lb.f, lb.b) + function Base.show(io::IO, lb::LazyBranch) summary(io, lb) println(io, ":") @@ -145,23 +159,28 @@ and update buffer and buffer range accordingly. moment, access a `LazyBranch` from different threads at the same time can cause performance issue and incorrect event result. """ + function Base.getindex(ba::LazyBranch{T,J,B}, idx::Integer) where {T,J,B} tid = Threads.threadid() - br = ba.buffer_range[tid] - if idx ∉ br - seek_idx = findfirst(x -> x > (idx - 1), ba.fEntry) - 1 #support 1.0 syntax - bb = basketarray(ba.f, ba.b, seek_idx) - if typeof(bb) !== B - error("Expected type of interpreted data: $(B), got: $(typeof(bb))") - end - ba.buffer[tid] = bb - br = (ba.fEntry[seek_idx] + 1):(ba.fEntry[seek_idx + 1]) - ba.buffer_range[tid] = br + br = @inbounds ba.buffer_range[tid] + localidx = if idx ∉ br + _localindex_newbasket!(ba, idx, tid) + else + idx - br.start + 1 end - localidx = idx - br.start + 1 return @inbounds ba.buffer[tid][localidx] end +function _localindex_newbasket!(ba::LazyBranch{T,J,B}, idx::Integer, tid::Int) where {T,J,B} + seek_idx = findfirst(x -> x > (idx - 1), ba.fEntry) - 1 #support 1.0 syntax + ba.buffer[tid] = basketarray(ba.f, ba.b, seek_idx) + br = (ba.fEntry[seek_idx] + 1):(ba.fEntry[seek_idx + 1]) + ba.buffer_range[tid] = br + return idx - br.start + 1 +end + +Base.IndexStyle(::Type{<:LazyBranch}) = IndexCartesian() + function Base.iterate(ba::LazyBranch{T,J,B}, idx=1) where {T,J,B} idx > ba.L && return nothing return (ba[idx], idx + 1) diff --git a/test/runtests.jl b/test/runtests.jl index b5ffb36b..510d36ab 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -682,3 +682,11 @@ end onesrow = LazyTree(f,"t")[2] |> values .|> first .|> Int @test all(onesrow .== 1) end + +@testset "basketarray_iter()" begin + f = UnROOT.samplefile("tree_with_vector_multiple_baskets.root") + t = LazyTree(f,"t1") + @test (UnROOT.basketarray_iter(f, f["t1"]["b1"]) .|> length) == [1228, 1228, 44] + @test (UnROOT.basketarray_iter(t.b1) .|> length) == [1228, 1228, 44] + @test length(UnROOT.basketarray(t.b1, 1)) == 1228 +end From 1d06a984e180aaadc9f83c9b62419e6d47a0f3df Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Sun, 26 Sep 2021 17:04:22 -0700 Subject: [PATCH 10/59] optimize TBasketKey unpacking (#115) * avoid slow merging * faster than in the try block * faster without dict * single read for rest of tkey bytes --- src/types.jl | 68 ++++++++++++++++++++++++++++++++++------------------ src/utils.jl | 1 + 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/types.jl b/src/types.jl index e7e29da6..613fa5f8 100644 --- a/src/types.jl +++ b/src/types.jl @@ -59,31 +59,53 @@ end function unpack(io, T::Type{TBasketKey}) start = position(io) - fields = Dict{Symbol, Union{Integer, String}}() - fields[:fNbytes] = readtype(io, Int32) - fields[:fVersion] = readtype(io, Int16) # FIXME if "complete" it's UInt16 (acc. uproot) - - inttype = fields[:fVersion] <= 1000 ? Int32 : Int64 - - fields[:fObjlen] = readtype(io, Int32) - fields[:fDatime] = readtype(io, UInt32) - fields[:fKeylen] = readtype(io, Int16) - fields[:fCycle] = readtype(io, Int16) - fields[:fSeekKey] = readtype(io, inttype) - fields[:fSeekPdir] = readtype(io, inttype) - fields[:fClassName] = readtype(io, String) - fields[:fName] = readtype(io, String) - fields[:fTitle] = readtype(io, String) + + fNbytes = readtype(io, Int32) + fVersion = readtype(io, Int16) # FIXME if "complete" it's UInt16 (acc. uproot) + + inttype = fVersion <= 1000 ? Int32 : Int64 + + fObjlen = readtype(io, Int32) + fDatime = readtype(io, UInt32) + fKeylen = readtype(io, Int16) + + # do a single read to get rest of bytes into memory + # and offset by the 16 bytes we already read + io = IOBuffer(read(io, fKeylen - 16)) + start = -16 + + fCycle = readtype(io, Int16) + fSeekKey = readtype(io, inttype) + fSeekPdir = readtype(io, inttype) + fClassName = readtype(io, String) + fName = readtype(io, String) + fTitle = readtype(io, String) # if complete (which is true for compressed, it seems?) - seek(io, start + fields[:fKeylen] - 18 - 1) - fields[:fVersion] = readtype(io, Int16) # FIXME if "complete" it's UInt16 (acc. uproot) - fields[:fBufferSize] = readtype(io, Int32) - fields[:fNevBufSize] = readtype(io, Int32) - fields[:fNevBuf] = readtype(io, Int32) - fields[:fLast] = readtype(io, Int32) - - T(; fields...) + seek(io, start + fKeylen - 18 - 1) + fVersion = readtype(io, Int16) # FIXME if "complete" it's UInt16 (acc. uproot) + fBufferSize = readtype(io, Int32) + fNevBufSize = readtype(io, Int32) + fNevBuf = readtype(io, Int32) + fLast = readtype(io, Int32) + + T( + fNbytes, + fVersion, + fObjlen, + fDatime, + fKeylen, + fCycle, + fSeekKey, + fSeekPdir, + fClassName, + fName, + fTitle, + fBufferSize, + fNevBufSize, + fNevBuf, + fLast, + ) end iscompressed(t::T) where T<:Union{TKey, TBasketKey} = t.fObjlen != t.fNbytes - t.fKeylen diff --git a/src/utils.jl b/src/utils.jl index 8c63d395..8aeb1e39 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -60,6 +60,7 @@ function JaggType(f, branch, leaf) # https://github.com/scikit-hep/uproot3/blob/54f5151fb7c686c3a161fbe44b9f299e482f346b/uproot3/interp/auto.py#L144 (match(r"\[.*\]", leaf.fTitle) !== nothing) && return Nooffsetjagg leaf isa TLeafElement && leaf.fLenType==0 && return Offsetjagg + !hasproperty(branch, :fClassName) && return Nojagg try streamer = streamerfor(f, branch.fClassName).streamer.fElements.elements[1] From d2f639cefa221806d7401cc4f3695f8d2782e015 Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Mon, 27 Sep 2021 10:27:30 -0700 Subject: [PATCH 11/59] default byte for fIOFeatures (#117) --- src/bootstrap.jl | 2 +- test/runtests.jl | 5 +++++ test/samples/issue116.root | Bin 0 -> 26048 bytes 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 test/samples/issue116.root diff --git a/src/bootstrap.jl b/src/bootstrap.jl index 8768bb34..dbe24b66 100644 --- a/src/bootstrap.jl +++ b/src/bootstrap.jl @@ -639,7 +639,7 @@ end fWriteBasket fEntryNumber - fIOFeatures + fIOFeatures=0x00 fOffset fMaxBaskets diff --git a/test/runtests.jl b/test/runtests.jl index 510d36ab..4c8eed05 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -544,6 +544,11 @@ end @test rootfile["tree/trk_algoMask"][2] == [0x0000000000004000, 0x0000000000004000, 0x0000000000004000, 0x0000000000004000] @test rootfile["tree/pix_ladder"][3][1:5] == UInt16[0x0001, 0x0001, 0x0001, 0x0001, 0x0003] close(rootfile) + + # issue 116 + rootfile = ROOTFile(joinpath(SAMPLES_DIR, "issue116.root")) + @test length(rootfile["fTree"].fBranches.elements) == 112 + close(rootfile) end @testset "jagged subbranch type by leaf" begin diff --git a/test/samples/issue116.root b/test/samples/issue116.root new file mode 100644 index 0000000000000000000000000000000000000000..372753de433816bfe042dc3c8bce4a612502ca9d GIT binary patch literal 26048 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 Date: Mon, 27 Sep 2021 13:27:49 -0400 Subject: [PATCH 12/59] bump --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index cd813eba..f7325c2d 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnROOT" uuid = "3cd96dde-e98d-4713-81e9-a4a1b0235ce9" authors = ["Tamas Gal", "Jerry Ling", "Johannes Schumann", "Nick Amin"] -version = "0.6.2" +version = "0.6.3" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 514187dd4ca882e431b58710509ae8cafc05deba Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Tue, 28 Sep 2021 00:34:18 -0700 Subject: [PATCH 13/59] basket cluster iteration utilities (#118) * add some functions * add tests * fix --- Project.toml | 1 + src/UnROOT.jl | 1 + src/iteration.jl | 26 ++++++++++++++++++++++++++ test/runtests.jl | 12 ++++++++++++ test/samples/tree_with_clusters.py | 19 +++++++++++++++++++ test/samples/tree_with_clusters.root | Bin 0 -> 64439 bytes 6 files changed, 59 insertions(+) create mode 100644 test/samples/tree_with_clusters.py create mode 100644 test/samples/tree_with_clusters.root diff --git a/Project.toml b/Project.toml index f7325c2d..9396b1a9 100644 --- a/Project.toml +++ b/Project.toml @@ -10,6 +10,7 @@ CodecLz4 = "5ba52731-8f18-5e0d-9241-30f10d1ec561" CodecXz = "ba30903b-d9e8-5048-a5ec-d1f5b0d4b47b" CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193" CodecZstd = "6b39b394-51ab-5f42-8807-6242bab2b4c2" +IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e" LRUCache = "8ac3fa9e-de4c-5943-b1dc-09c6b5f20637" LorentzVectors = "3f54b04b-17fc-5cd4-9758-90c048d965e3" Memoization = "6fafb56a-5788-4b4e-91ca-c0cea6611c73" diff --git a/src/UnROOT.jl b/src/UnROOT.jl index 2e58975c..9c08e5cb 100644 --- a/src/UnROOT.jl +++ b/src/UnROOT.jl @@ -10,6 +10,7 @@ import AbstractTrees: children, printnode, print_tree using CodecZlib, CodecLz4, CodecXz, CodecZstd, StaticArrays, LorentzVectors, ArraysOfArrays using Mixers, Parameters, Memoization, LRUCache +import IterTools: groupby import Tables, TypedTables, PrettyTables diff --git a/src/iteration.jl b/src/iteration.jl index 0ff09b13..67766c78 100644 --- a/src/iteration.jl +++ b/src/iteration.jl @@ -307,3 +307,29 @@ end function Base.getindex(ba::LazyBranch{T,J,B}, rang::UnitRange) where {T,J,B} return [ba[i] for i in rang] end + +_clusterranges(t::LazyTree) = _clusterranges([getproperty(t,p) for p in propertynames(t)]) +function _clusterranges(lbs::AbstractVector{<:LazyBranch}) + basketentries = [lb.b.fBasketEntry[1:numbaskets(lb.b)+1] for lb in lbs] + common = mapreduce(Set, ∩, basketentries) |> collect |> sort + return [common[i]+1:common[i+1] for i in 1:length(common)-1] +end +_clusterbytes(t::LazyTree; kw...) = _clusterbytes([getproperty(t,p) for p in propertynames(t)]; kw...) +function _clusterbytes(lbs::AbstractVector{<:LazyBranch}; compressed=false) + basketentries = [lb.b.fBasketEntry[1:numbaskets(lb.b)+1] for lb in lbs] + common = mapreduce(Set, ∩, basketentries) |> collect |> sort + bytes = zeros(Float64, length(common)-1) + for lb in lbs + b = lb.b + finflate = compressed ? 1.0 : b.fTotBytes/b.fZipBytes + entries = b.fBasketEntry[1:numbaskets(b)+1] + basketbytes = b.fBasketBytes[1:numbaskets(b)+1] * finflate + iclusters = searchsortedlast.(Ref(common), entries[1:end-1]) + pairs = zip(iclusters, basketbytes) + sumbytes = [sum(last.(g)) for g in groupby(first, pairs)] + bytes .+= sumbytes + end + return bytes +end + +Tables.partitions(t::LazyTree) = (t[r] for r in _clusterranges(t)) diff --git a/test/runtests.jl b/test/runtests.jl index 4c8eed05..2990c11d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -695,3 +695,15 @@ end @test (UnROOT.basketarray_iter(t.b1) .|> length) == [1228, 1228, 44] @test length(UnROOT.basketarray(t.b1, 1)) == 1228 end + +@testset "Cluster ranges" begin + t = LazyTree(UnROOT.samplefile("tree_with_clusters.root"),"t1"); + @test all(UnROOT._clusterbytes(t; compressed=true) .< 10000) + @test all(UnROOT._clusterbytes(t; compressed=false) .< 10000) + @test UnROOT._clusterbytes([t.b1,t.b2]) == UnROOT._clusterbytes(t) + @test length(UnROOT._clusterranges([t.b1])) == 157 + @test length(UnROOT._clusterranges([t.b2])) == 70 + @test length(UnROOT._clusterranges(t)) == 18 # same as uproot4 + @test sum(UnROOT._clusterbytes([t.b1]; compressed=true)) == 33493.0 # same as uproot4 + @test sum(UnROOT._clusterbytes([t.b2]; compressed=true)) == 23710.0 # same as uproot4 +end diff --git a/test/samples/tree_with_clusters.py b/test/samples/tree_with_clusters.py new file mode 100644 index 00000000..8b83fd2c --- /dev/null +++ b/test/samples/tree_with_clusters.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +import ROOT as r + +f = r.TFile("tree_with_clusters.root", "recreate") +t = r.TTree("t1","t1") +v1 = r.vector("int")() +v2 = r.vector("int")() +t.Branch("b1", v1, 500) # default bufsize is 32000, but making sure +t.Branch("b2", v2, 1000) # default bufsize is 32000, but making sure +t.SetAutoFlush(10000) +for irow in range(2500): + v1.clear() + v2.clear() + for e in [irow+q for q in range(2)]: + v1.push_back(e) + v2.push_back(e+1) + t.Fill() +t.Write() +f.Close() diff --git a/test/samples/tree_with_clusters.root b/test/samples/tree_with_clusters.root new file mode 100644 index 0000000000000000000000000000000000000000..7748315056f3a97e48fac813ec55d30eb621b4b6 GIT binary patch literal 64439 zcmc$H3p~{6_rLos%3VkpV-TefQOae88F!PCHc?G3rIeIX6rw?7hRP73p=_;6?QSV_ zQA??1bkm(xnceQq)~3||Jo6cs*~u<__y22NlaH--*Lmig=bYz#-skzdYpECZKi-GZ!r&}zxE7B=a5Fbwlu^$p*MATC;p&54^Ojp|Kr|i~o^7LM<1Fc(R$bLsIEOV07Y1xu?{fJJ> z5uJ#4ce6J$+cvQlTV=9-<}~9Je%rNw8eUa?<5BWNsg?##T>r zhC(H28CxYXXD*v`n(pqv7|o3=*?IqXtB-6EuhcS_x35q(W}j|k8EfH*Uh(2*qut>r zyDnrC`h4Ie{yp{-8^R_rC;0Pzo6-ktQaR@*Mr=)pwNZTkSxvDdskvl}gi-`%Rs=mQ zf?Vjid*aG9-xukX=VmiU$5uq#_1xM(n8M7*lb11ZEJ|7z!JeA0PL8DFc2ZKW5vFqT zZOF?xxFX6n9>LKnU$HUulm1;U#WKEaTODs-df5Dkof6S9wp-2bbtf&CbRDG(Mv40& zqa+Q4KPZXKqDEN>J}C4d@jocMn3%+6^g&69iy26w4~kQ1<+gh4mRFzcAJi2^KX^w{ zUXb*#kRk1?DHWtlbI!rOf!|Y*3N$!SY;Cmuob`+5!yk8V_}LI&s_sHJ^LX;Mo?XD8 zvWcn!@k$iTBnHHb2| zaelR%EmNub%}v8MTd#66R&tYyxmh#2GZW{{{mH0vZR>cwmMKN8#`jtr9b3(7+e{LU zJ&X!(esS^CabNdX53N}5I3C$@k6NPm4?h^)hgB1H5(#V_C4UuN&%usAl#|#iNh}g0 zcD(0CkPk$@t+SLpC9m+)Jb{}m$7?uI->#aOwCnq z^0LC)H8IYyojh_?E4?PLZ7UfU+0OHGHMw36Y&$OFEX~O+SB2H>6?nVbEiS$#cOkE$ zXeJMwByH4Dm9Y7%d&QpUs3Ol7j%J8f^pav@&*6FgKs4K{oC^h`dbtt#rEacajS2eJ zrX}F;;QD2T=-uLU*}T1TYRJ(GJ{(dw#iVBTj9$;}c!E5$fK3jAIHqK)6QrpHdE}*3 z!yhSGHUuS3!Etgp$FP>NLy;h9Rj^&I#N08WA{7kUX?vSnGA5z?Okszc%2Ac#6N#DA z)RE)Q5psM%G)u1+wP~>9i{-$d_dCA)faAwVIFBWDY_3pHNgB7N9 z;*FTj)wm$M3Db3k%9y6iE=Dq0+$=+#!6eI@W#%z5$kLS=0gM8&Qf1~fMjBZ%GQ)8L;~4B)u5D2_aap^dOuJDeCfnz-I>|36{C8m@Z>Jg zch-EYKE*bPudwSW@$Gp${(~$jW8x=R8evC~!WL8Jis+&a?frusMdO^9IdJW@_LoH`qX<$}J9{K8=V~J7Zxh<~r7>G%OGjeUw*GA&oL>yKeI-35>uV8o8LJfWm{Bh4Du!_Ne&H+A0Jex-4bBk9<-%f4gGy?N8d!=efMy$rUF@)xk@ zNH@rnD#^%4E^KemUb4JIhILdza?Q!;$v?_0J0b)By!?m^b(Xg7$1c)zT~GH^=4f6C z{=R22n_$Ld(a0;AMyZsfJc2Qm6+m7=HOiu-94AcVuwu#49HW$`E!zo3RxGc^T%XwT z=__LcfmQUXKL1xWRHnB%; zQzo*>G2+hTFW978Iu6++oSo$`TSl!IiA@6~Twg2)7c~3r6IY_iNSn26k*T@+Zm9v( zrZP4qNxSxDPfUDMP}DvzL*2u{&m-ug<*>FTFM^s?VRPf+Y6EjUu7_PsTguz4tYTi7 zT|&3H^l9xq-CkNAt9r%ikBmprOcjb|L3UPj5jAS)XqM@Z=1PcWG51$*5|+znOt{OB z=9^4ZxHT0$*-&ia40YUzAmtfJ59J}E54>Hm?$)Egs}4^-<{FOM+NNU{!jHitN$sma*| z>5H8bYaGlr;Tk4T^tH({jeb@{H>Qf!uI-F;TC$h%Y#M=OxTa}b5LvU4T1}kG)TrKd zh>=B0x!a~v)CPh1{N}p-Gh27v=jPUN?Y7ddyU9D0TGz037f)6BIeR=e^?r9sUH7)q z7p;bRZDzqOQ;*)x58R>O&01)0@=<*4RF|2sZo;lFkFBIqp6H4WD~d;<*B0s>Qgja)g>y~I&czEtmRvOu z4QB<+meCPkXVY{gNMl<&%ym80!obf7rrpTjOLfRSiS-r%a5QV#xi?Gq)1%AYT9i^a zz2s3fdp-aCz!G7F%Wc})Ot7xbw`lY*sc9`5&tq?0$h#B8s~GbP+o zY2Qe*GCbC_-GVTxy5LY_;Z^lEzLQWdMeG}s5V>1orHqyW?3oK@9sGb7^?OCk5|z-A zSTsBsfNUht7veJLFo}7aB3hhw5`cSd?jN&1K&$IF;r&!;LdA8}o3ZD%|T=qV; zWUc9rPsZ*Fxe{jH9&D@1oZDNhb$+3LV(-)^Dz$YL0Z)GQZ?@+tVYLXI~9^a=JIYF=UnY(uIw%@?%p{w;zQ=0?>@Ff%c&g9QjPW6AM1QG z)_n)h+pZJ^SI@L|8T9{ay)MN42e@>p8U7 z5Au;duix5Vz6rd(M8+ZKv%c4%vw2hBBzrGBaa8?xQ?8TcgwD;w-srE>NVd638`{#V zV)x5Bnw92RUE;ob=o(`7FlIFrlNj78nZ=x-q+dGSL*POEl!dwXK`u% zVV!>*8{3!=qKD!epxlHuqo|nY{hg(v$A~Ecz+M=TJF!U zjG)q4(CuLt|FB0Q-)#c&dmo`nM^I*KScuv*wBJkgS32g_7vS9T>P@|Ma+!kNsAOM* z6V}zMQY_@E4H}aTMkQO^g)hDaMnTR%~;r-W#6GSqc*e{)=cd2Xkg@ndO|O`hPm0L|GYSHYFb-m{+r+8dp{uZEG@=_S33q zLm*Y&%shV|>75N}0P1+yWpI=JkbSe)oD4s>aD5N3D|%^pW6$B8!}{y{tDZ{dLX}A! z(-UTWK34;%l$!bxK0FJRj=0kGEiymtc^5ID1Sd<3q(V8gP<8#Gb%cE&zJN+JtzH|1_Ry&Gyc6_qR}nzYIx z1Dv@Nams>mz6;(I71SKO7*=XEpK3{8$^q>&zDG-v9u@!k z1JqW>C2r>Zy62Z4X8KGmQMnR9SsuS{VgiSe*uDGejM#-ce+hZjW5YI^*qyqNoANd` zRR5GD&d3yjn8MDYiY=y&ujrx<9n=HPauUjXWn$|TZF$ux=`TK%NLu(ON#1l)y7o|K zLCW$~O4po}*qf{fe>LUKHF{=R6q^^DlblobDg=kO9SI6u#k?B3p9Q6U>fGBA`?YGh z=an8PPU77;(Yp1IQR3;UCv2q^X^v`PS>)!jO#&rYSMXE_}mGx_Op6}>$LFxgs+4qg&T+C% z6P>2)+u$ldq+jDUG-$p09}#H=qft(tIBy!@(A!BD8;NkV2ROR9IbY|ugN|((Kk?jT zdrG2vN<(U1kD2`kk^8ph=eCIv6hNpQi@Gzf#+J^Kadh!*h&_2Xd-{H@`EA+vx#Yc1 zmwY~npGbh!6ZRH$Y(4c*8xy?-0h~ie!GO0wY7WgM&n}EbF)&{!1_WS> zzMvs9*s7spVD#T(;BtWB;>9Z9?Y>*&<^*h6q_=Kt=q)>KXvL@q4)iK#Q3%Kt8{lkZ zcZZC7Gbe6s;5+XEN9e`ahm~r1n0)p`exGxQtht)B0Ci%(@7V@$l*f_Sc&f`K+|mZM zS2_REiFrlZ#5U)U;&F~_OOCq)Lrj~lm{Z!QteprtRqdF_SIp<{KxFEVk zPI;qh5G!aqwZUBX;M1ZT$M+c(aSK=SE|0IURWFBz%LmH`(eK^%9xscX=`U{Q23{eY3p6o5G5BsI{vwnM{Gc1d&jsKmyAGU5 zTJy2?1sml8Nz84i4|3#J#;{GY7FzIgL7D8*R}cr2<%f$(Ln!!C!fR%GL|w~-hK#p< z565lR%D=*5OU>_@p8Zjh@tY2c17W+gFnbKG|1-Om8$8oiS z$>s;PumQ#j zG|+jQ@ZeoA4srohN2{des!Ed0okBOP?rm6J+)N`_o6sIl`5A&hf3?TfPo5H#3_K%` ztU$?xqYhc7DFga5Lto(3fviWMwupQX3_LGAtN78x?{ISv%^? zJn9>k>mFR;?q1W9^_)lBQFvOt#DHY#_ToX}gLR&=4sAO>b)PLc)t&p1M_SZlSaGG} z$305VxDEDiCAxog4g-(4DO5L%#eBplWF)?l**L6L{+wF2GGA?32q-^+T`7b<;zn_LJDR9t9p=HL-W2fc=s;0Bomhy{f9g=U0U9FfUCDj|VDy2*^s z`cKd397@tTa5hi3=B)SC<~t8BP!Akq=c=DJ-7(@^Fp=vL7w_kx?U=5F%Asex-_oLG z0&Dz<@>Pb_8H@0w$J2IL)N+;=%q#d6}GCJzB2EjgBZ>WhXPxeYaj?{<1Wm+ z!DbDe;s%{`RwnyHV4vR`5W38~MMG}x7JgtGP2l6y21@~@9&aphR$&4ks6KLf+RRr4 zCMQqYJt;0}-+%H-^OQBc{)b^aa)tr@E8?>J!B06vZLW*c){ZsS(4 zjX2IVIOxrATzhapuK|D>=Sss{xm?68q2a8YD{#=9v2uNk1AYx4)HqL?aka}0+;W;x zwewH7?`S5~u5WQ0yF$Yr@LW|g{Fzn_*Rp1>s~B~!`>gg)Hy#=4OPhMt=atyhCiedM zyIODC&gT|AvI&gcAJxlDFPM>{iq2o#WAyHp+Ll9}X|Qy{XMJ64F-`167j@_wum7yy z!UuM@cAUR@vEjCMzOyRf2zD9{mFeo0VV%|9lXRn=mr?(oxqK4S$HBvad^YoScgRRu zG=SXQXZ{VX4nJRbHr1tuqjYo3#qaPa0E7!y0m08yfZh%El%b6pRJaOtX-d8Q&TL6r zb^yT_qY~=%@z=McRef9%{nu{GlaCggT?^t+6s#`&w(s9r69etZgH0KmkpKI7n3hA; z>bi-Ym3aJE$CZZw%XBCGF=p8Y0`mX4LWxZg`-n_O(N%%ycSlj%>M(t}!xo1dPwcep%(C8C ziaZ+PV8aBJ!_i)MW(DVWER!^h-xmjQQD^FEKY{b?UG$y3-OZ)5IM%h){F$cCd-EzR zH0Xa=pOP4xKBq#&SM;z|o#22yN9rqr07!uVDA3-61wdG+47|fvNp7e=o2DJr>v{OE zLd%nn7jqBgvi%h92VDF-B=zv`DGdRYEODWslUXo$9Sw{btpsXZ5T;Zd( z(6XsqDS-&FNm5B?ao+=}tVBZ9rVL?SDst#~;DB2|fYF2=DYwADs;cob;0? zS@Nt6%`3uutle1?N&_nm<}fBu)u0E@aNsBdjgmp&s74yDVAyh0xCX(DNgOq3!ZT)C zDFcs^VQ8hQZMcLn#Y%-`Fq>g!r3PJihJCd%5GfhP)v7^;%NbLvRoV@{V@#}8Yd74; zaEwv5H*jYd#i-gFhBK^VR8ATMGR$MtP8!Bpzwys6gN=SEw=j{r*P}u6seS6fwWs~J zB=ekCd$gJ)w2tZZXA}pXvD`E1HFNKMGnXkI)U=qq{Sq&3NgfFdxd^K!y!-28>p0O) zbUlaOCcORGKLT8+*Y+j!Y(@ohv;P=1dbD|jh*1WZF_Q%Q9peD(9S_sw-~#Y?rha zAb4u3geJx>#$bz_iCS}Gub(QM9o>2`?;1IyezxKt^-?wkn?76Ret~|lC^Bkr+mb?% zht6e}0heq7G=r6>8^tB*)VTrP+oD; z$<1wlZ9Dw&M$oY*%} zh%M~&<3!*lLwBF(Kn`SQ@`&-|2hCLxWVQH`@PfAK6M?4^>ALx;+UE1GY~ozwY$||@ zrd`{bd;>^lyvv8y>vrB*&^9qE!ULL_hk4HCk8f!={%mOo%O_kbpx=Sls z-+%Xmel<|o4L0jqsQil$IDhdZ43Dom3-}#?ckH`4jQid` zlg}3NiKKUnI=LZ>@lz-{*YKn-#}e zbrjoyLa{AqAzIa9)(zgUhK}t~{juGLUk4aYz9pBDp#3&Me^+S>pG3`1ZLvfmylsF* ziooj`;t08`|9(feZ){*-$ODTgL+epT%GBR{_Eb#IY+!sLc=^x;Lh~${?g}_*wkZ(O zaK>y4pqJu+l|v;h!%byRqPpwgl-Op#NW)EJPlTZY9ME$5F+3TZB+b-{2()Zr4DS`` zmQ+~S#ppYGiX*+JJ8-50K3B(es}CCu@HIVT^|b3joQ$7hOYC~s*1_Cu&+lpgf71hd6fJ5-Ud{s0=K-1Jj5g=@ovA`%U_8h5E z!$Pm!Ztp!EWwNtM!8rKD_Sd|T9@oY*aW!hQy3qiQ8z7NBvmDr4rl#m#$Wzw!Fp;wv zivnP_Pyh&asi}3MMhzVRvi$*I76XtSsRLu60c59PkPn>8a8nx`4v;&3_eMvBporFgtM91$LhlA)*iGqOIj70v&FJbmWn>kzbl{?q zwJYfXIp32tD$}p!)R6Te={7kPWVOh2Afu6Wx%AkaDzX+gotINb)*l{hy(NFrZ)>Bx z_a5J`hDtb2(WfipDsH@*O~*C7nD+Gfrk3pN*wW~lR(;25m%V2yQ1chIy{fsF22o)h zZ2wDfL!s?TSUBOGl7KCxouKVKJUE9DJtwGpK|??ano1uqv@ryUI36^~WV4!PoM$<;L{||D14A^>;YcTmo`ZDDH9?9q+Bll*; zuW!cLNcab%AUGs;Rsp^wJAuq)5P$vQfnN80l!axD=Fo_d}Q>ulqcPJ8pgiNDv@1LJ(44`E+6T=!8 z(6TLvz(S^_A0p0eyxqJ>y+VKS)J5VD(} zZGZwmn0qgvTTCC({y)IIHya3mc|bYtR+wPmY@~c0(T@*Q88kx}tZAVu%-s?y4g3Fr zK>@JH`tXM4<_k^KNgpS{(B%JI-MHb5XcjOti38TL6)6m7O`Bxpu8A8(GXusw&YU(8 zh9+^kH1lfGI^1-cWwrZgoNQM_n0Q8umNB>RmhEppJ|$|^w5EJ&9yjJaH^oJLpH#V- z>&IQ^ZJz7~;-bm1yIuPX5~z8dd1dQ~2Xsvv_VERp1uF8|4JdQ5;9){{!3q z7yVzP1pxlPd-gqk3!rmhK*Q&M85$FUKcfh`uXn|kPIojd-(H-j23?I(%U<>tXl!J3 zyFp`P#vvSXe_@8HxxlK&*mHQ>KV7H|1?>BO2CJDL3J;$qBR1xlg?a9uhc_0~&$!V$c66`S27T*(6-hSzxxztQm<-gR0cwVAYlyMO-V2&D4z^g_F&`zTjynVq6>#gDNi35tMn>Q&66_uxa<6n=`_g z_G-2tChuUKTu+*i`k;Jk9_bR91QRnPn0scYT_eHR06WQsR6zD*ryeKWCcCp!Vo5b* zU-mX03FZc9TNFuYjbz%^LBtxtoTm#d$%3@2(Sw$Hh;*aTjg}Hc`l-=}w(TkDZKHG7 zmT{!)MoQP#J*2CRq^{%zSDJl8)c4_+c5i##a9#VmW4gUhM&C%G+P$q0?m1Du133;EzPc-v4cIzm{$VC>dF$6TKbq+ zILoZJrb97T^IMO&={r;8`of#kB+MA+A4g);piK&Ezy1qIjk4LD-Q@5heCpk8m%2>< zWx%gLk_G(P5Q@sZZphJjvz>k6h<5A1UiFt(3^4d{%oUjl$oGZ$2ZH0K3#48CYfw{p zXx|^qKj61%5()!=e{h|yP%P#TAa$9Jzdt(6Lw(1`t2GjmWhOylTg57vubDl|{_>_U zlMhdiupVp*dwa%ww(%xQjRuX?jQ{SGk`6;NjXLf0sGJ`fHQUpl=F~Qhe?^?pWf0?( zjdSn9$GBX@g?Hg%oIf>NIH|x)s0T;u(E;+ThCj9M9NTQY?cmyCGxA}Wq{##Py3*+Z z&+}vPn!-~h0KOi;n1pm)2_PR}JbX#hmFAF!{^tIZF=L3wM`BdJ`y-mQpK@x)8sJ&4 zT$BZp22#=%{P9Vkk2|?eElHX<>s0ZDy_C9U{4sJ+5!*$Ne zMhfIFu#*fecRLy=%RRmyaF;%rgPkCApgOBUQjbg2S}^Om(&+u*{&R)S@&bAt34T!i zYgq0-xJxPFKQ^sn^W!6mcg`Hi{at~07Em+tHLBRB@5wpVsC|;YAm?D?_2%~4X|4cAy&_Hf zVGYh}%@oF1RjqU*d4?2Ioq|guZ($QpMDG?NC#z;`?K=?RRSiiD997lKa4Exzsv^`A zntaUtcfY-EHs#W_(35Gi{2@K0dv)f0Nk{+8cpJ9-6$DykCPt7pf7D;t8m^U?TU_ZW zsnfQ7VRzB4Lrd(YY_fHRl@s17r(lce_7HmxF97;|1z}ly)f9HwqQ=jAL&f+*w`Ist zkQ@w}HMq^6GWfozh>cWJsIe96M?65EiJ#e0q>1Taj7i|So3EfgymBTDD*r1d)-lbM z!B_rvoUy{JasO!~Rt+3y`m8D~TSSE5((L^UZ=u;vk0zOEcy= z6OYH6M}p5ZgHceHx17^Nw*-}(d2ngf30>&UYOVq5P zGk_4D>knPZ;!@n?qw1HByf|MMUFoL*49sjgAIyRTK;*NMH%LhNZh0uLeAJ!^jY`&? zwWs!n?a;KbzG8Pk}`6axwa&s3!tMlx)eDoqAK3=5_j zj2Sa#P?bS6px>W6GynQu3a1$??eD6!3R?HfJzfsd03nEN);zB2Mu(1GV%r9!ePO&J z!Z#$AtrpyUv2>On{Z_?go97@BlMK8~$=;XQy^m>!zH$ z_~FbWDm#V4`3)O6hfYmIxwpb;?Zq|9x*gfeWpB?@M=>C5l?~=39wVy%2U(>7{U>4v zWV zR9(Dp{P7kKIHn0gptG`Zl~RBWCJ{i8VV?e2AB)sW zprr-^RuLsvCboCl?!S|T>ebPlDdiTQ_`~1GoZ-!XcNubBHHesyPHUrF z*XbH5i_05}0$aGW6HsQo!bGhaI8K!WKNJ#B6dVfLXg>`gIaZrmy*yDot_f0!$Jy2^^#dm9}rHJIs|7TL#(lC|9 z6SjZXUa*em{2;c>I=I_yjPZw8kR>kHDyE>-);w-f*G5WZN}$Te{s@GRPIA?8h|;QeOqrG$bwOuBABBCS)?s? zDKIsP-2dV}9pwJPMhW;7K3_Qh4!u>08s$Hwxda*IHNnYG()L}#doT)n-|92UVFDO+ zF0cBpkm0FT4IJDkCLTb*T%2MX&&fpSJD@LiDe$Sn4EXeAXU8G$pCeS+2}r=cw2^rK zApRwIKWIJG7iQ4U;#%nRLk1GxgX0%0M#Rh6NI8Jttl{I-LE^YwMkhP-f8}jF9NpNw z`~|rG2Gu;pS`XEM*_!{O{tR~Vd2f46EY-~I(D@TZy&Tk!(d})mXPsZBH!FdfQ`X?1 zxA5ls(usN?3I-J9(t5@YsPS;>CZDP};lGV=*Pyoyo3IG9JoVrx>vl@E)A2`~e?Vle2 zE)5`*0RbSU@_m^K9hTElO*$AnQ?zB9$hvWE0Z=+cF3(5_V2oMl&W}gyW)!{^d z0NQdj*Hv2{wn~P)=gc`^9c$+&5${K@l7`<2FZt857G?_;tUVfggTirksQWq&z8#!w zR6Wd$Y;Si%v4Ao_PL(=}fIvkYiU47#dOBuJ0AnOp4M5d=;Q;AYeQN7uS3RXxr?9d< zl}oXcFC@{qZN*PrT6a#zsBueb*+uGgrkZ>=q4gC&D9XtC_V=OJrYjVCkVxHNK zZI(%CXTXF_f}Uic(?bQtxqG|iec%81qSwdEA&|&nsrQt5J*|hqGKyvs3!3tvjg${f zc~ClIK~tWhU6~I}d5T6Q3!3sEj+75gd5T&j3!3s2U2Z-!jCv>YCX+xB)6|&~Ulty}&$sjE@>!p2 zXTOEj6Rr{jf;)3ZgntfH34G57QI)wigd5Ed6`9kLQU%jAN0L8&4()`AjXx$PAW@Yi zT(%!_`~&pQ<^Q5;IZz+)f!(f&TrYv+@7uE;rYreMF)C0c#4VV$*PehJU)U%|%t-=a zH|M_w;Xgx%^MFx(j(-5DTuMScr(lZIE%|~O(?BpoNhhM684~s8l9HhC-;evadjv8u z2(1g|q>fkj_mZ!L-qZaCNyN~)t5}7Upn!Gb8fc1)63)^yF8`}6}~GpS@t$U+z`4bx6g7y zi{(+B-PI|Ja4J?k4mz)9rCQ}k5CjgtQF4EUDYx!i$vg9>q{~(F17L$N@xTeQ z>bp?vIoMT(j_Q7-?y5J!s(~eYo4fgg!g9XySt!PmMF`~g;!08WPccmpuP{cn10_{` zJNfclRcijncOb$muQr+_ELnSQe`~)MDYTWD-d0HW&xcbC^bstdwHD3=Hl4@E{Lu(! z0zQS@rqREMRCIztp#@*yzYiS&0~K8#^V0gt=<^Gqkb|-5EfWyk5;bXv?nEMR&(J?5 zn>f9cQrRFDdh6$LO_tg3iE)2EyJI_Q;?l6Xpd;)#9~Oe1GA)aUQyJbQ0L%WARyacS z>f~mx0i=4G%IFwj_}Wb6)0J`(hwO$=p^>V|K)v3IGyJl)#?)gDYr9k9d2V}cDxwQo zlRwd)y;@)U&S}TBvhd$ITV_TOLu|_JdsXlJVVzL#rnHQsR!iM`Ka#B0(*gRm3j!P$ zflCNG3xcoV75oMKa~RIj4_15W=%ju8EYIgu=ivH*I>3O~82EF$or3!G2Bh$e{Ueoc zK1uGBycb!D*!R(B`0zM~AbnpkN)8S++n388%2Wnt`P;y>3*RcSgvmD!f>@<0xCpMjtq|Np@Tpeu2v_t-U%j# zc5VqPep1TXmLWH%RYj$nE+^>Q5P^y5Ai=P7dMPbeC#F(3(SC8^(uLO(!XXbhTd%ny zVW{By!gxE8h(liO&oEV0OCK3z{kIm(z*tTleaFA``Wu>0RMoa@e|)?Qq+;xPIjkyP z_~Rq<)KL%!$2JkOW>Mrwtm;n$ErM~0LPaGd>q_X+RQI-P znZkK~P{(FZ_Q?wyF5JAg+XG6W3mn7p?QaIldL~nx{!Q7li(qDG`@8cf#J@*PduUn$ zue68@hy_p)@^5I0CW7&pIg6b{%^Ere1`>f;j0=*G`LHkvG%$gIEWP#BlwaS>m;-r$ zP6y_S3j8K0nx9p`q2Xd;C~DZW?&Vh1-LxJx|4j1&>j%0QY6JVbk7f*YCf0fEOkeeK zDYkPN>B^ko9FNco@<~t`%)*?+W;>arh4Rf z<|F{Npsg|SI0tENWhGJ@3ZWE=HJO>WZU%2G3ISoO+%RisQD0|OA7Y(ymL^jK<$trE zIBw>)yHV=B%?QUmZzOJo+ zjDtl$hop|ERqmKIO9feOq}x@WRcdPI6$_A6Yze8Up@78u3J~jSTdwq3wGWzWW?p8Q zpGS^Zlx=;EnAL*R#_FFf6iJ)3SDu85!%UM)haI|nknmV7BpduGbgpLgju}jZ9Zd|asIsE>?cPsBcZ5n^2 zdFrSHzX_j^PW!*|s!{Iy;%F)4Fe#jPpWxY02O>3wyp;xM-7>VTv{*)?7}C}1LAbqP zZ@r@P@QK$??1Y}HyF;sGFn1*+Hxrk`>igmr&l!EV^H8L_Y)>f!{lyl2Q!h*Pa;c-s zc89DEJYH_u^B6>NTu;rs5348~17vJn!&hO?!RtDl$nMiYUc_}dw;iY}Y=yDV)LWTQ zA0h+Q5b9eVdA;NAOUnl_O(YJb6o`7@E?)s~RpcWfU7z%9d0SOwHRb> z5h7EONBTUhi#Z0V_j)+Y_ChkZmWy3=;pjG<-;9EBST{gquJ{yN3JyDi ztr|K81|9QbTo4^0QJp^40&*BbgSZvbcLrUw#+O@u0oXdv1&KYQ5WsNjLn3ro+^)|z4+9{*mWh=y{!nh zuDT)LHa&s5hnh#Iy4(@B2jtOUb6njmuF$Fra5j_YJ?W_4gY4|nOUB$uCx3z^6b=9n zY*Cl3#-4*0bvOYaK&{W;x|KiAI>p+lQ%V!lL&m>OOfd&Nf;iD2h=U*@5RW<9ie|AUfi~>c*m61q!0IR*3IJj7&J(j{x%)`05(V!FP~BaF zl8#1DFct7Caz4yN-?fB!CLXPK!rELho{J>HC>9bX!^c&T{3`Zz9emSXRoDpR+gc~0^kZPvB^j{9f3Ku2?w|HyJqE^a0mLOQ#$SlL^mhkhj=#7CJWY*68{ys|Iqa^P{=uE7x2C zI211G0N*ElwEX9riw13wAa5R4Z5S}gj@Xw~m`GrR-1%u$xg5Kp|qlVu62hElR0vkE4BOw?tUI|txV7#=6 z)=8lRQ8{$F)$;Tsvk!-k#Wg%_l?Zzq`WVQ_uS5Rh znK1y%qKLMQb7`4RiRy6w{!OoO1S@KnTYKl%SydD$v3?viqIxN6%lq zKRy2ADn{9&{#8EW8H4hDCtzZZA}|Uny2Y+%fQSRF8`&!T;7R6l6Q>ZVkSE+GDkQ!SB|!TO8lYdLMRnYsDk? zzaKWt85-4^K}Na^rD_dPnGm%YO?x9P#;6#LlQ=s@A?uj?>NM!!bY@OY&{9b^;N#fY z>jr~=i#|^>V-l%2xFi9t2f=CM)#Vv|GubT1*{SyrIIsA0ooYJNFbYcheF zzARwB=GsW75F7R^9hLg4;mZ<}7IyFap6BBYtZZPSZG1YK;zLXlaTR}TEn@|^-QYDH zhO6|W*3ImaZ^f~`_=hsM%HL3HDrVMNLGyliW_!W;eKj!8en1ekD5XEM#`KX zicU2ug+^~Om&{yqs^@KkMQ9>`)|;Faygzx$$!Bh5(8*Hi8AL`t`5zTDh9~~10wQSt zq@W>~F1dEQmsE8G+yUrQEoK@r{Acl15? z1$!^Qy&G82I(=fK>qdvxopDdcZ2R8qBCMV8emNUk%{amNe( zweE#$nS=z^yF_1CDOH`VG@+$X79Kf!l|x~`!TvOL#61qV5^I>r!jRo^eZcKd>t;$Y zJ!{o3KT3`4 z8-aYr4Dnf4+{phZgAPxW~!d+h;(c zo5gM*#W|bzROs==Zl(0MSwDt#6Lyw4{IwK|7o2hiuj+7OpbxdymPBlivpTdinJvG3 zULJT3X619}~LN$K807)$*BMP3gK7>i;+xWJo>nYB?+I}FdPfkWFt!Jx6cM4Qw- zIH?i5wj6lzp|`pSj%}q#!gsRdu8^2cyS+6FEK(gP4&-0Hij*8)q)zE`t72yDwc`ctCy#SR!th-p8rpG(0;-12MIp2-h&n( zy?)WAE1(7w0x7hZ-@E+9Jp{yGkIHPjGfnq)TRP+i+1jx+aOISfbB6Nr*bUxsNNb6; z=BfVAb7snjxJn?lmwRBz9KuR^0>32iPHBJ74@xpR4F; zfU7L}iW?pevek@1J@2TqZeI((@18r|D!si)<3y9dJj|*@LFF(!tNPCZaGUx${@s1@ zu3D;Lh{7`G3;nIpjV!Q1>k}@J2DN8|TBG}hk-a3PLXF*+h z{o+m6ug2~R>Dj*@#tn+ZQcYdtJ^a(=rJny`k6JL-XEQIfd;Du4r>VpBg}Axg*WW*9 zOxm-=($|WN_D12jvzQFRqFX`EUhNm;t&JDM=epya-I~HZa(t zh(B&)dFi*!!?;X+Fzs}MC(`Fgyf#}4!QRpHNTO$fn%cu>g-oBb!f z4T&M&2LUKA1#&A0KzVtP+d%-zYXbQ?JL8(yGV(pp>h#hfx3SZYd(9``0j*9i2l7pJ zCeI7BIza%+OQx{}1faa!8gIc>oL+>+YajsS71nqc1faY$8#y2V4^(9b{@L*+Kg+h^`!WHw5l%vit(vCi~MrskE;sBJSzn4S8U@oyQu zg+NZ0Z&P!e&9vfqMDv_O0tjE5H8 z#8+L3y2hW&@+0!eTjQoqKmj0ZmVlMNtrs(IcmW`67J{zj3AO`+6wrd@pJ^b>YfnFT z7!tqowOvTuN-V3Lv)uWrK94NSN#+>VQg$d3B&`azi!`H7HBHjHz}Nd1SQXu)jPyZ> zKv#AIa&la@dACRcn1Go>zb0zda1sD;(iQ>dI&sYXU$>J$Ifg%PsnS1ZiN?-g;26*? zl^|!L)SDT;_Du8a_g;a4ajh0Broi2`$%a`OJD}e+l_& zil7GZZg7u`M__-js=5&gk}qO?slNZP8yn zaYy9BUBw5S4hNJv{CL3g1rP25@Z6O7>raVkuX$dU0Tnt^D_s+MXdof**LR+!uyDd# zWhl0k8787jI*bI+FS+s|zb^V8C0D$x{w}#vPDu|SXiy8Uk@Hts^xb9L(bhfqQt~E( zHoBMGB+@q=kyKzf5m+m51)jNaqL-`#Cwi&a}JQz)6*MrPLM^fG5illty$W4Zf&0l zR}%KHC5zCwbnCt5j^%n)k0)PewM?HF;XVC!8zYhH0{3|C`#?Xuy8X6Zo9>lJ(sw;& z(LIZ@9mPHnO`pTc35UQEY%Q||OFDQ>hxV0$6wrrTOOd}BZf&+rLo~8!0<&%k>>Ad= zX27gI+*-Z)o8i`1c4?sq2o&nwSYYPts1h}6=m;2?IOu0v&pB`|)CkDcD$Uk29&eDz z8qmja=M&Pf)=kot^xw4hfB(Tc_+EnJu`Ta`l=&vr^r@rg$h=^MPyU9)rYRJ7S;Y?mAxT2wDQOps$ni39c{j@!E0u**$F( z9X%ClPj2bw8d{Hr#S;z!!CWpwT69^569j$0^%?DN23)T`u8o2~IBBCg7G8|I*mHQX zJ#cF41Fn;$z7=q-!O=xAAPig!6djq>qE-zZ1A~EU7iSO?_*^^Tz_|thxKs|o{{Y&R zh>3OxvC{Zgb`n8GhOZ?9lK-fKf7>TNAPVC%e#j?hiBD5 z7U%=6B`MzwxDNiTk0L<$N*na?!E5Jn>^a!1p(9}6N*lz@M`}vJ(sRaI;B;WPM%hn; zf208!rO9?n?B2{saWjBs&LJ_r_CK9}ZS&)eowYSDDz21iP63^?J^MEGEbPCNW`K(f zyp%6qI*ydl3{(yNqr0B0GuzZ^Tc#h)x@hxk-CIfs&wCc?-u!q~(%6*rE6**c(@t;U zZ{xUL(EqZ`9A7rpk%~_K`VF=xz0Nzfq;Gtxf3Dfx2bNFRUm~#u-K`|Lu*1m%eH{U5 z>u=T(kkB&}4TEI@v%0>{tUmPOTmzq>_AS8c3JW7-mN0m|95W~X)JV({1+O!p#``8x zAs!yaD7J;OnkImCL&x@@?B8T50UQHU}85 z+hOh$E3FYME^#O2w~QrqGhV&EpGCe|!&GNZsy9`8Z>f!64*85=kBFwujG{$?vOB??wgQyh3A(i9pzKZnWp{3N8IZ0Kw|Zyi zoIm3pJDK$Cl$fh~mjenSdzd`nq*6XEJM%+wv;$|1Yki5XLYpMeQ6uBa$1I*>xRB%^ zIlt0>B`l$Ec8kImwcyc6JY^ue^}*Mv^4|)+eja3k{9o9r6__=JBl7{!chps((wi~VLS&sSdJpx8Q@cfT&tNFVQv~pkr32vuF zN>qYdIt4g3x~x;~0^HIDLk5>-r>_tGFl*z~HMizJVI}k2ru39Z^->FB0`Q49JJwxI zC2eMl)l%tYJf9GrtJx)LlKnc#*Kyxv!y*c&HWU=_Dgv~W{~A_-H&6=o<$z?!0pAG0 zw%uti8UxXoHO2KKF>4?OkXZ=9mcIC{$k&YL7IGpdy6qV`1_eCtE+T==QVQ zX!$>1z5FFm8_394_T0;~2G3Ux*}7tt3PVm+BOQlyIOtMzwMC~h;WD*5G8Lxo(m+bC zJK(Z*S*!k9df?k>zd3TOmzw%Q17|PKb?P#X<&WthZ%>x*Wt(`kjjH8L-^nff{#qzN z*pV#iSy(#Z?NTstS@OH+q7Gf$^}EUdgk3Q6>mlqV9D_yY-bbK&Ek_Pv2kseT0zbQO zTnonNO3#eMs)4xfgRpJ1znNsnhl46$*58geR+u?uyG6|!IsyicIFJRLL1F3h9=2iB zwgktgmlr+L2yjXUezJxJ^lyUwfx&`7zkyr2H3C*i%SDB>`<`ZXQ3%$o_xr<&Q_%qO z(KKA!XI1AioIjqR>e`80jW+9{uK5!otf_=FxDVr+KM97|7wyTH?J1E=Xa$a@XPiuL`d=${->I>**&jH+ZTd5AuFsw& zn^!(FMJh|ey@42PLHE5BUD%vbRYM#er~iufgnLH;%-v{^)M$!?E#= z-%2#Z^@)aPbjM=WloyZ0s)6Y4gRyNCznN&rlebzZy5|bP*w&ai6>CM!8alcMk09^? zj1M&#Ws4Uh)sPLvg&T_DqE00A&d+n0h|XMP?B=#dAAc?jjNd;7WZ^$8iIcfxWeaO390G#X z+<#egRfi7kfe`3Ju^YabXvlVTZPCD9hgov~^`y~jScU6=RekarY2QpVWU`emiU47f zjtyo^WgPY#Y}L>aFwn|0iAhG&rE5NGr=_PO`t>0LTyu#E@BYRY{OS(+IY=9wk6@eA z>5q19>YTdc#Gl0Gg`g`i!+P@`RYv%5FEtoaA?mBaJnK=^y!`RMy?sm3z1_;cxDXgR z6*r_!z4RM+4htU~-SweDzo%RcFzn5WH9PK`5?`nrB|i9#cV7!a#{KP4+2Z^eh?Otw z|AMvr{ww{z&r=BBOo-*0v!1B`ug5&(;8*&8ABH{Zn+b-j579^dFAQ@{#jH9sBL5#a z;UEYbDMFvhD4To~dNv5cPIV)Kwm=Iuflva%QAk^c-^K}DLjyr*wcg)r>!!fXoh;V! z$5;Gp--ht@{r-QdLy1Ug2(CUm+nlVvA74(_&^{dM4!3+f=dpjrILc-odwJg4otD!! zwY@J50S$_UiuYXMN`S4P!x6bydeYCUd}BL6TgG;+8`>C!17HKTs6RAg&%v9@a1uZt ze694&R9MC(7>Wi!JZ4Q*+1FXshg@9zW`ZGSwi}@U5GE<3^bN1Wg76;vSq+^421v?g zeK@b7k_>{m;D*kEfI#To6Q`E(5BEjrBIi~OgBl59pdY4)u4cL6#k~`;`<7D-&GwW= zeXNJ;$tk0o3cDD|6mhcxbq13nZ&sMc$e>7976dQ~C`y%u*BEIO$;bj5#xBaJ$im}{ zY>?F|h-DO0#&8RHjHD(p?E*yxqe)J?a62QtNs3kA#b7llvI?6SsZA0=1s05*O$tGU zhZtE+GVKLXjH0H|?S)SnDQr{Y?sO_hXEqcxEbk_pIPkSK3|!)PPCIxmkv%1fuXM%3 zWgqZdwbBwvKdbNC=)tju6Mllqa=F#I+%Z>O{dU3X{lDVAGAzrjX&aD`Mp6Wco9+(j zE(r;xyIX0H5J6fR=}<}(Q4CNdRJsL}k`n1I>G;+KxRveR->c8@zK_?j53e5z`<}UH z*36nX&y)Rt>xUYJd-7Z7|Iht^6p$?c5eycy2Q)uhzxR<)#aT!1AMNBRBuBAV(rr_Kxkk+8W*+U|T1|1w|JPlA7-UmtER2UjJRJdMVzJcZofA0=WpGk6i z+St%}p6ewyl7*AEnl(a26p#wSfb+^>7K8)ml{;k}2Ao$8-8vjNuN=yB7;s)W?CEge zybW-~VZeDCP7{X%=WRet4g=2HfRX&emRtjx_Rn+%4e-~b^~UI(qyP!r57cm?TP-4!~)Gf#PmhHRfd9dmsE9_6pTB-1t0< z@u^^ifnq@m3Y`+5xsxHy_eht|Gk@0UubBTg$dJH41{recE7Q+T?RO;2i+{NP{)$xK zrUpS>*CHzYDX8m0WoG)HLK^>(R3*P<|38GZW4I_b`DJ!3aGC#x7X?rP5y6zg(O6Cv zw2)?m1hGFko9O`VwyK#SWqBfjM0^EH8peF(-2cv+jarU+XdogjJZ{R92lN)8Hb{Go znsPd9VLH@+LGr?O7Tb2NpNamcD`;I(PX{eb#TZQ8eyWiL1EbgpA;YMz2n;%T20Kp) zt3#ORnQN`*&%>w9XnzI@JZb(nj#OIuTlJs6F28NBA@p#m4$u7}zjhq;Q#?kYXr~X6 z^AFK7-vS~}hQt;;y+3b%$k*o3kj_cClrDlFI)`s+Y&r ztsf7{Uoro0HEWGOHg!#S?hKIXZ&#WNM^e2~IC}pm)xRJAKVE5$k#Gr?lE}a?8rZNr zqx!(A>hGsNG+%Wba8k+b+6 zXvs7hn&+JW_BQ`7NOU!vt{4wQqW-J%Wej*fn}5KOOs|hi);}7`ub4kn)P?963*`P3 z9ixFP0c8Fsvmb#YsmkS#-akt9@8*InP_?0$x69wHCMC1_Q{`QTx+?Rn)>^ zs*h-YijGksmgHxr7CaK_4cXEAcbyvS8ysOp0jGWvG6f3KAvYda9YTbF`BDf06trO> zKDY0EnnV0sEHG(p3t@yvbS%C8UxJ7F4=zE;J{{P_uBJ9SxqygJ>Hx4-3Pbp}TR9ZB zLLNGA@>m{XNbQ66jn~yF^09n4AN9cAlxB>DFn}q-wb+Y}D=z5!lc5A3>GJJyC-z63 z{*IwA{wa#fg}di}_Gu7uq30d+H_-D-7y7>vRfydH4gl6;5}X*E{co}{##WL4Z2m$= z!c?C>djBZZuf(*!%s-6Ld=XS61A!VPS51h^)_-S+XH24$&Khti8bjnVd`PM8O_WK8Or6RY!9%dGl1q! z#x!u`K~K%`;L49W{de>KpDg9S3A(~NNcpq4lG^s{1YbM}mi=I0fecf4LKMDUCswX2UXwn7#L?OT!)j0m(qUNpP>}y1rNuet4gU zZ~~j(j)$3&eN8yVk+H}?Ajm$*4T+ahkkba}8B+h3P`C{S06jwrpl6JQq)90^Xpcqc zwJ?iuC|ru=jHkO|H1Mv-CEswH3sked01gj&<>B}|(qf5VO&4e?$BWa2&*u^#Ig>Yx zzsF1vV(3!QZpJ1)r&0;jck((Dex%U)<7v!aGycD>Ge^fHK%98+hbGqj&}bk7KbiP| z;UMT~kT`n(noQplpKlfF1n%z`0{jpt8rOe`jFF0-4zm1{w;7QmVH$~k7V4Ky{%^J1 z=o|l^S}wG5_67dlO~V`a&E-q9Cb;+}Eo56WSd1)GEcS5|vZKJfJ&YiS5zO1e{*U1_ zVzL{~0EB@tYA1;{nB|y>1!Y(VO72uRRJ1Ba{&MbhC&byDPUMw)xeI-%fnK6{t`DF@j6m%S=D}bkvbFErh=#)!)7c; zrJ%i<`}dELZ|@fYzQT|h&h}N8A-lu))%Tv+6t@_ZQMd15j2B3mKfrzH;&HhheQP>h0zX?tK^1Hp=DDv(F;i$ znUR-eOOjUzi{;!?#^|a-U7Ay)$-G3?djdSjz+C!nwEI1ONk_Kc;aW(;y(@W%oJ*r21P> zJN}N^L4oz~`QeiG?+)<2M91KU0Nv(if{;`05~q0D1wjL5Z!Ulm-4S!=dC;Tr7n7Ab z?$JQFInA}>L_OYlLl4!RpS|&hu%Ckmw)|e%DpCj4NRL9iO{g)5vH0>ZP@x+QUt$$K z#w$Nwr^Z2zE|()?2my4}a%UJqK(4I)BcPM6?>%04zZ*6uVp_V4YY!XQsf^k zMN%nlN95`^Q=tT8PC*d0W#`19sq`hM=N{63g)t-_(1@$W;WsLBQuF;1Lf{x|TI$eL zp1)5OdEN;)JLLhjWpXdz?EKOX{~;6)a)6VuI_^lE&VR^%{@txl1c1XDHE;j``*tpIj{yKNjk@?Ei<=GA9HGIlwQE(FB9g z-y+zU#O-WQdI)rj2hGVd#~aiTlm-O+3y(vpQ;T_DBFFyBgN#Ei|NzBHvh8TVSb+-s&k$U z0r5v_{qS4(|34g-1n({Z8dQ=pZtd`MfaxnJ^Rs0hF#Kanu?j)fQLZlo%$5raOgV}L zIRWv68czG)+LO472*(}Dbot;lT47uz7-tejgB2$%t}(EMmJ8zavv~?j&7mq*{i{iN zNd6V*NK4lQR>bv0No$E94>);MOE{A1Bk|Gu*BtwAS2d6V>8G^h$VhTWC!cNbA$Nfs z`cN)P;Ma+E%c(u{d1`>rOd8FZvGaF1ZC+grW1!;wj^{q_A_r*NG!>>~yy?^5d?5Zg z032V@KFpzsk9z#1Ny|;nbxGfP~FJf-1QXtrNJ~haBGM!5?p&Ao2??eIG~J z7VimY>av!e%rk*M7U$259&Xv1CF>y4!p3x_dp4}Dp z=_23jN-lDld_UZvLXHnB`Z)e#kY|1>+;_SG^k{B<637FZJ9$#xInrg%@!|4ocJlqC z{8sm84NA7Mk;60#Lc`e{*znH{0+g0&k`VO&n-v3Y0yy?PAo$_evO|eHzoxe!a zNf?LZsSpOr6eigQ&pTxV&qMh_?hU>G^jc8RE+RKuA^G|EPdpN;_qXz&??QdM)kM?~ zo7iR{Cl@1}vZYAaL3TbOGyv^ab-yWOQO}Mx+D`4e;Pe+^xcIvyg<~`f_eg~4X?U%m z+~+Tn!mw!tK<9^wa~=}vQ;eL@+== zgFM@6%e-W>)z;0zltGPtn)((t<7oEZ5>zlradb}o;~~`;G#A?z|300wDHQWZaBjfU znT_8={)n6$^68UcAjhX}-`gYUezd=T_v!DOa(_)yGa}#tDg!{X3)8p@*w+yeYTvhF zvHyl|A*aV_X4we<`Mf$);jV5Gb%+L{mZLUP6(9Xn4%(T<0mz)*XUPAj zDKs%^vE%543_E2;50C<2RA$ToDFB9N#t4uCU|eRb04V^5ZpHwR0$@yLEC4A0MgmdU zJZd293!RIuXdt@~LK>~f6|SE0RD;5|mH(s2l$@wb9{OMl>6O_Udhz!7yF$h1f^`Lt z@DBZ4%%`yNbLkX1(Z@qRmX(+7$Yi>VlDc4b_1e-j&CDjm1iv>$;B86c{+xcf{=sXyaSshy5tE8)8|HH)C3v0 zfaGp10bvM(6<}l1y!2HSru^9lQjUfC2mYg9@qur3>c}9l1)e63OP6&-RK*U#SRXRc z9pRX=0T^p(G@A6BrT>tCt}8&Ti9VmMxFZ<&HWMIU)B{5z3IOo)t0EQT11GC>Qb$q^ z)E>Qm%?EzC*c`_-4QZn%VA$*?0Y%m!QQ;AbLDpGl9LE_E2Kc8B*9mHz%pji5vhAVB z6sS*0#^o5=)N$}vu}<#{Q9DuI#u=kBo#59g+9oF~McPIwZ&{%lwGYGBy%I{9lr^USng3 zO*WHrh^4wE?;lS~qn4~8XRk(eTRz}9ElX#zi=1O8RY2p72wE!6WW@$MPO9)6yhAn{ zRYgYyCNAWahfg8a= zceRFBm~SX>;NBA8zNINb4TVPN2GTvzR)cU9Ig!W^2eV&ec?H-$dN_c-)da1A6j1(6g4R|F7aL1eY1h}}VE z0&n)O!46AtM0QV#@fqS>V&4({Ht23-IZgVsGPVLkc3Or8&aA+cq#=Nk9|0VYs$i&( z^c5`%92|-W98eN5+o5u4;Xr9@?HhNPk4WNwb&0;v>yZ~(i?Mhx^f-*-J)eVn*{ znXk{BO^=$sDePv!J@!lXcTDbvI7#7ayuW{cpwQo(;e+|@*?SQsJg!r3Mp))5D=U#Y!lM|3ga4t}D7Y}mCrM#6pz0ppQ9kz~f(D0QR`u=Pw9o;q$R!PNPvR^B8lmpt3zW>;Dp-Qm)U!t=4Mx7*Jum};6+ zsySR4ePrts?(W^kc3DVAwbkJ1_1ENKucRGm_Q-Yj1vb&jFdn(E`_LiQ^=5jRA{QMf zw6OA~+m$9Om136k&b#=SR1acw(Ymx)J8EBb)1AH7y{?=n@@@@7akx||Gs~;=)ay^q z(1&>X(Cy9@Qy96{U{g=O%|%nqt43cFA~nwCAWIO%YBhnxJ(hy=9(Yk3wQL zL2d37vDZ*g!uXQ(1}-k?9k&I0_=Ofz_;9UvPYVh1cx6cI5s{N>@bfqVv|EhH(6wrC z@0ep7d)0*e(+}X~vHsH!JMRLv$z!#KM2 z7|ANu;PbS>Q3+HgD`Ooi@k5;A*SY3f7a2q!h*q-6Zb%> zT4nI;)^!@C^;DKu_R`7Ni4=-jp0p%Tp{X3fs%^9!m##^)yCP(tTORRMzfxk^C9+3x zRGVS^@k=FN(+tHynn~t(7SZU{ezRG`t=8vyS0i`3c~&0CJE^|5%cSp$s0vy>uhit8 zK|_Qc-=lKpVq_ejVhmpfMU}Jv?gGwk;_lF;T`5*!)B~2pGi*Cr%V^t=DJpA)8G7H< z_Y5p#+_2MbOz<3csC_uI9OcB5`TAp8DNkB3`M&)82JYj*haQ6$IX5g{RZ@^MH)HCK zOM0KbKE_iUwc@_XzZ@0fSFSX#B1EzJ{D3=uvvGDTZ)aRRe7rsRAX&M#y`yqVr>4Vqrpr809( z1oZ=lI;ok$>jSg?+qY4!3$t?yxSQd4319LIHjWDt4GE|qyIOz0_KL2=W0aQ4LQ-+GZNhj+YonxS9@<7G=(U>3?TB;N_j#)jIFw zP?%aDAb*we@bL8ZtMbRbf<8lz4^z9=3%swR`@P2?vg1n5 z?hVo&2z(ul?oM6A&v}MuMa(}6aR`=&(_Z8uUE@9QuW=R(%t1d5WMeihEhDpXN@iUjvC~Jl}jYUlnVWN_n~N@*|S* z^C}I3nepz<2n(MB2TIkpb{6ko7v8~UyA!3)#y6_MuOcdFxRt-$w~8;?jwfaOWYiEV z_VLJL6#AhO6^CIq_mQ?L`u*x=rwu~wubCJJ!|6j9Xv0hQQaDz^IQBez)oFKks;Cw8 zKn)Eq4~m|`N{7)?42qtUAbEnKr=qp93x>Ljlew|Ixs$wug(I$%qpOLXCxxB0gRQw4 zcsaX(xT=o_rz8yGYOAhKgkI0(s-5oHy*14le&v+@(oeKQ{MSYU`CZfPlJu|N<{dP< z?QVx67`kUMQJFrO?&-KYl&bF-J6cScX`jOFaFHGr@qyZ11O%8)mfEdAs~EA-%>95s zL|h31TwDo_U8MNo3mcWmI~G+IHgPLCe2Y8#ZNifS&h5gJipmix;UXPWPcEzl*jp;E zVeFsP^<@o>*S*}~GI9$>SK}8VzB?4zvlU>li^cZ91Xm_%OMK-e&Z5vZcxzW)bY}#YBBQKjXL<<8q{1lU#trQr?$?B7`ivR1r^LAag?T6pT34wuf1>}n=+P# z;J3rqs){&r z?w;pb(MX*ya|L^B5yn&z209TX+yxoUjm8=k#u+ki7yRoMJHd{k%Sk?_U1K!^_zr7h zflKG^O@87Rsiz)G4t#Bgi@bs-&-n$#hGb8UPaalvNkzl1G%3Tcz|TfNINvmEY%EZV zT~1nN8%d_W3Ss)CVZmGu`Lx6V?~R@1;d#GQqt4c#*H$BF7#rNxgQnG(s`VKz*+>NQ z?}IVlFKqQr?(<0B%f7uX@mg=s#P?O*y9$c5G#tz18$P`7r-n4(EFO5&tADkD41xN(fL#_cSfgEX>I z1Hxc}FNO740vI3tooo7+-gDE08^0hd;9sv6bCK^#4a(C@%6q8TrLLXQw#}0_t)AX7 z=7svwrN{fm-NGN+W4li!sKV;WsXErjH47VU^nR6T5wR?B7rj~QP12sZ3Obo7(`;it zWyy(|wHcpK(_+)mP!9PR9o1VgG&Am#VMOs!y7X!ATTm z85=s!*4AI3G+_6MycNJ?f5op`hHPab%;D;QWU(gs(#(Fl3DzDr$YTjZ0ddCZHfvx{Ra=TUbb|nN4#56JlkD%WA}l-cPgc5dvtqW z^y_ldCg<6NK;6u*(?4u(xn%w0MRxj(g%Nw{AKRJDp z;Z9r6&RMuJu{-(Ehk*dKorA;S3|mB91JdP-9$F7=&a${)Y+WR=@l7r8%q%GA|A65| z?aeuoKOKls5{QSMedUV3orvP3)+_ui`I~f0_pzR5MCPuBGY)1eu@*npV0)leitlGc zzfY^a>zc>^l!44d>q^fvsi(YO=cQIxUleplIl}tbNS^w|G*-5lsq~p)d#PU=)ii#) z;CTCk3x?QWyeH%5M|7XP$F7*yKHiq^LE9WmF!&f6O?P@FW!Q52x#@C_(y$wwsnX5P zQzPBI`K3*3Abbau;< z^gxb(v!bnB$gxWxwo~DYr43R#(-t~O_JGMN`CV~GpDsOiKss=bY_ zu=nOEuKnX@^ZXYCQ$?Mqhu^iJ7MG*-<**lBUAi7=-R3bGFq8v7C~_)+CPB{eG-X}! zm)9>!-+GsI*V^`+Gx?OxFTzH;ON(*m8S#h3R{Es8%^_1?spis@?y01+V<il^5&ety1?g2`%&cb(6A^5W?ydez=w~ut!A9c&fdrlEPaCbu?Rj@ zTlebJ_I93~aBGM)2Rk8@&h5d>Tcwo!!WSvP}Ebf{B(|kd~?2ZQk4Kk9lj>EBIN_ zQK~qy%?Cy(ql6sm&hfw$(n%~H=9#nvC!^hIM3Lp1U3Ie^YH8eMSoUbdIfI6xDS~&c zcVhX5J1LXu%f4)x^Pz~FLqdc?zSNhcFB^}C4qBP{;OdOta;)7eP4uU;n-a(NC&l9O z>3`>zI^`vA%uLYxk}?Y2~x?aZ0*0^ajK=k#q%# z)qT~$)DdGsSh=<85pOy9lH@JMq^s?whp0#Is#}2RMd8QJcGi}0OzfVtD^!Ye$ahmR zaEE+}>Mi>NEpd(s!biLiCxQ|;;|!Ls3fCH){gg-)Oj}T~p@~gA#=BiJ2FCrvP zrY;MN!S?g@S+Q<}GK5~c&-m_bLcNsed%Mf0{V8!awGfoaKiN+et-fekvPK`$bLZi@ ziBu@6!bDkt6=XhsKiXcq-flp|5!aya5t3+3v~(`@7eqXF=EQd)X@mCq@VJwANK%{= zlbm0tXpQJ`Mbaxqzkj>a<&ml3buZnuKtXb707-W!yVXK`I9k@YG256XB8Xp1J4aoJ zV&WOxRXx|)@aD7cFLW0uvroj1hjpIiNWa&S8hNdbjmG?Tu~dG#DbB_QFZYI{Zom%y z6#`m|8Q`aU)l!z=8FQp>{e}_5vkZo~R`V9O<|Xy-s04m;9_6Iqk|MZGHUXPp`Y$Yh-Go4kE_l z_zf7zhYXv_&%K~2?tDn(zB_RW|8>@@Noj96o7Cs%h@58EueZ4vDf3^T%rJXluYIk& zD~bnATO=*acy5giI zfaH)hoIANoFRa>znl00uV21o^A^VhCq1rO-m)|748Jfn3ln=c-FVFC00sA^#^foH) zyCHeeK%FnTT5C7&v%)_<`-bN8Sm4W={^-yg%4frbtZBx&@0`obT+xlLPjikM{h*L68F6Bb8i)@=y0PM`w6RC-0D`c zE&{hTi||C+22}HW@z7$EfI6AMcNKO5rojUlPXxKd zVy=VLS(){EKBMe=@_z9=Oy7_AGn>APbGz)bEIcygYX4+g{-;E!Nk!&x>!`O~hagO~yowKO-qrrhi7};3h4gJxU9Tko(Y!*vgk(_vM zv*)|_uD(COo$aP75i>?ss`Pa9k|@Xs)6uw(B4+pgQA==0f8xV?&bJL%rc;w=gM^ub zWBOq37Ah$?+31{a)kNds(!S0htd~vCO$Nr{jnXX45x;)+X-@G|@SSj7dkc?VTH6}I zQ&>!c^Md@!2Jb^fO&==^=EIKUxY>FaMx|K)V;ku9Z9yYzgBFQ#mfHS!2UDly}h}$-j4lH zyVA%V)xxd?Q=PYsg_kfovQcE3Kf{?L^(S!py}h)|x$*L?TB})&&bVk)p{`B#Gfcj_ zo0FK59Tycl2=c7zCYrMn2@gnT0`HNq?ujd%&NrV+iH;a8brQ=)AxpGgmt=y?+`SU< z@|u+y)=r8pEmI(zQ1vJDB1vA+&cf*DZ*Frt;GE;Q5L8K>M*3h$3qN_w@lA{0ay^Tk zX_v$EVMC6sb~bj8+i$tPoLZo6XVr6hu}J#rRb3s0{3m7wYqbE^o9%Z?uh$|s2P%d5 ztJ%1P+jpqRVn%G1-JKK8Q?#A3v-UPDZTQN@33)71c~YBIaGR{|&pqtv@W8Hon^qemI;OrP!W-=SeH+$`<{Ro#D}4IdE!3j$ue@4)I|0be`5TT}BKGFpHKNA+@N+|s zBj$yVy!^8D+4r(;BCZ}7+27*t2}^1ZP&Yb-##0)>;2ZkNI?M*LYpFSc{ zyOWo+9>EjGG@kE1LNqpY3*2Y&9#V ze~!G7f$Wywpo#f3U8jB5(CAHSq>9@3$e!sJ^sTu3yoLu)yzUq)>*=#OXX^2baB%U_ zNb;L^;La$82I)RoO{%~4(O#VrgXuz~%(FDS&m)W@svU#|X^|?)H3Nf$=dHE2ke7{n z`Kx@>mxqmZE$xIQgD|Rtt9j~`r*!ZX7P^!^sHw*zD_YluN9^Bbxm(B?FCN0v6I--A z;{IY>TDB*%H+o-oHU10Xu-dDAu_2eCZEGa^nWy&CAdyXDj#}ev2bb4CyzU0#FMdL&_^ZoI= zvsi9Ov{7QSMRy2q44}@X=dntdI(9S9Ev)j}eb8aew@A}V#$YbCk<3;uPqVg47pxQL zA$0v%_gwwiYn7qcZ@4CH=Y_Hnt%HS9E974Yq(87P zFnBamMS(vs`Yx6vVY7|X6@{PIZxZ#bmISDi&8Ik1|T%zj18IZU5LV@Fy&wGQfm%uWZ9 z1SEl;&yfqyEJBK(;91pc)!8e}tkTC;W>!yIqv5Y~HANlp(G<0pjhk3ERFf&rSv`qa zD!Rr&cB^oT(RweRXqxW9=d`cN-AsD4vo|gbkCsxZb6p?l?y*y=lDJrUEwDrti?BQU zo`6DR?IwLxt?DifeTt_1`p`XPs;H<8!KD$Z_Q;gQy5g`N$Ec{H{f%tZvq`b%$bH0% z_Vps0QLxE`Ht+Aw-KS3X%BLg~y!l0!x1!3$bdAg)eW-;zmRW#nLp+mGd6x~DO%s|I zxeQh5;2009bWBi{ZinTlN+)A&XO3!OXKcmldq4n_x}JDqAaKY literal 0 HcmV?d00001 From 4a9c9b3a8a0dca6dc1b56edbf9cca5b878b11a50 Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Fri, 1 Oct 2021 10:50:37 -0700 Subject: [PATCH 14/59] match DataFrames behavior for `show` (#121) --- src/displays.jl | 6 +++++- test/runtests.jl | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/displays.jl b/src/displays.jl index f8f42228..b7572750 100644 --- a/src/displays.jl +++ b/src/displays.jl @@ -53,7 +53,9 @@ printnode(io::IO, f::ROOTFile) = print(io, f.filename) printnode(io::IO, f::ROOTDirectory) = print(io, "$(f.name) (TDirectory)") printnode(io::IO, k::TKeyNode) = print(io, "$(k.name) ($(k.classname))") -function Base.show(io::IO, tree::LazyTree) +Base.show(tree::LazyTree; kwargs...) = _show(stdout, tree; crop=:both, kwargs...) +Base.show(io::IO, tree::LazyTree; kwargs...) = _show(io, tree; kwargs...) +function _show(io::IO, tree::LazyTree; kwargs...) _hs = _make_header(tree) _ds = displaysize(io) PrettyTables.pretty_table( @@ -70,7 +72,9 @@ function Base.show(io::IO, tree::LazyTree) compact_printing=false, formatters=(v, i, j) -> _treeformat(v, _ds[2] ÷ min(8, length(_hs[1]))), display_size=(min(_ds[1], 40), min(_ds[2], 160)), + kwargs... ) + nothing end _symtup2str(symtup, trunc=15) = collect(first.(string.(symtup), trunc)) function _make_header(t) diff --git a/test/runtests.jl b/test/runtests.jl index 2990c11d..005ab4a7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -366,6 +366,11 @@ end show(_io, t.Muon_pt[1:10]) s = repr(t[1:10]) @test length(collect(eachmatch(r"Float32\[", s))) == 0 + _io = IOBuffer() + show(_io, t) + @test length(split(String(take!(_io)),'\n')) > length(t) + show(_io, t; crop=:both) + @test length(split(String(take!(_io)),'\n')) <= Base.displaysize()[1] close(f) end From fe7edd969264d3b5fbb45064ea430bb768ce3574 Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Fri, 1 Oct 2021 19:46:18 -0700 Subject: [PATCH 15/59] add interface function (#124) --- src/iteration.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/iteration.jl b/src/iteration.jl index 67766c78..99248be7 100644 --- a/src/iteration.jl +++ b/src/iteration.jl @@ -332,4 +332,5 @@ function _clusterbytes(lbs::AbstractVector{<:LazyBranch}; compressed=false) return bytes end +Tables.columns(t::LazyTree) = NamedTuple((p, getproperty(t, p)) for p in propertynames(t)) Tables.partitions(t::LazyTree) = (t[r] for r in _clusterranges(t)) From 6c481389aab9067e6d6192d3f3b90d6aa20843fa Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Sat, 2 Oct 2021 17:16:06 -0700 Subject: [PATCH 16/59] lower bound for PrettyTables (#125) --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 9396b1a9..4691a2d3 100644 --- a/Project.toml +++ b/Project.toml @@ -35,7 +35,7 @@ Memoization = "^0.1.10" Mixers = "^0.1.0" Parameters = "^0.12.0" Polyester = "^0.4.2" -PrettyTables = "^1.0.0" +PrettyTables = "^1.2.0" Requires = "^1" StaticArrays = "^0.12.0, ^1" Tables = "^1.0.0" From 4f6e62d97bdce7a6445d3e56d7f61d265cafea27 Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Sun, 3 Oct 2021 23:56:48 -0700 Subject: [PATCH 17/59] optimize and return VoV (#122) --- src/iteration.jl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/iteration.jl b/src/iteration.jl index 99248be7..9029fa15 100644 --- a/src/iteration.jl +++ b/src/iteration.jl @@ -303,9 +303,12 @@ function Base.iterate(tree::T, idx=1) where {T<:LazyTree} return LazyEvent(innertable(tree), idx), idx + 1 end -# TODO this is not terribly slow, but we can get faster implementation still ;) -function Base.getindex(ba::LazyBranch{T,J,B}, rang::UnitRange) where {T,J,B} - return [ba[i] for i in rang] +function Base.getindex(ba::LazyBranch{T,J,B}, range::UnitRange) where {T,J,B} + ib1 = findfirst(x -> x > (first(range) - 1), ba.fEntry) - 1 + ib2 = findfirst(x -> x > (last(range) - 1), ba.fEntry) - 1 + offset = ba.fEntry[ib1] + range = (first(range)-offset):(last(range)-offset) + return vcat([basketarray(ba, i) for i in ib1:ib2]...)[range] end _clusterranges(t::LazyTree) = _clusterranges([getproperty(t,p) for p in propertynames(t)]) From 2334368116a34ee7d55ac9cbf9978ae0daee8376 Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Mon, 4 Oct 2021 16:40:34 -0400 Subject: [PATCH 18/59] add broadcasting fusion (#126) * add broadcasting fusion * fix allocation and let getindex be lazy * better lazyevent display * fix CI * add tree displaying tests --- Project.toml | 2 +- src/displays.jl | 1 + src/iteration.jl | 31 +++++++++++++++++++------------ test/runtests.jl | 25 +++++++++++++++++++++++-- 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/Project.toml b/Project.toml index 4691a2d3..cd0ac60b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnROOT" uuid = "3cd96dde-e98d-4713-81e9-a4a1b0235ce9" authors = ["Tamas Gal", "Jerry Ling", "Johannes Schumann", "Nick Amin"] -version = "0.6.3" +version = "0.7.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" diff --git a/src/displays.jl b/src/displays.jl index b7572750..941b3b6d 100644 --- a/src/displays.jl +++ b/src/displays.jl @@ -55,6 +55,7 @@ printnode(io::IO, k::TKeyNode) = print(io, "$(k.name) ($(k.classname))") Base.show(tree::LazyTree; kwargs...) = _show(stdout, tree; crop=:both, kwargs...) Base.show(io::IO, tree::LazyTree; kwargs...) = _show(io, tree; kwargs...) +Base.show(io::IO, ::MIME"text/plain", tree::LazyTree) = _show(io, tree) function _show(io::IO, tree::LazyTree; kwargs...) _hs = _make_header(tree) _ds = displaysize(io) diff --git a/src/iteration.jl b/src/iteration.jl index 9029fa15..d009c93b 100644 --- a/src/iteration.jl +++ b/src/iteration.jl @@ -179,14 +179,18 @@ function _localindex_newbasket!(ba::LazyBranch{T,J,B}, idx::Integer, tid::Int) w return idx - br.start + 1 end -Base.IndexStyle(::Type{<:LazyBranch}) = IndexCartesian() +Base.IndexStyle(::Type{<:LazyBranch}) = IndexLinear() function Base.iterate(ba::LazyBranch{T,J,B}, idx=1) where {T,J,B} idx > ba.L && return nothing return (ba[idx], idx + 1) end -struct LazyTree{T} +struct LazyEvent{T<:TypedTables.Table} + tree::T + idx::Int64 +end +struct LazyTree{T} <: AbstractVector{LazyEvent{T}} treetable::T end @@ -195,15 +199,18 @@ end Base.propertynames(lt::LazyTree) = propertynames(innertable(lt)) Base.getproperty(lt::LazyTree, s::Symbol) = getproperty(innertable(lt), s) -# a specific branch -Base.getindex(lt::LazyTree, row::Int) = innertable(lt)[row] +Base.broadcastable(lt::LazyTree) = lt +Base.IndexStyle(::Type{<:LazyTree}) = IndexLinear() +Base.getindex(lt::LazyTree, row::Int) = LazyEvent(innertable(lt), row) +# kept lazy for broadcasting purpose +Base.getindex(lt::LazyTree, row::CartesianIndex{1}) = LazyEvent(innertable(lt), row[1]) function Base.getindex(lt::LazyTree, rang::UnitRange) return LazyTree(innertable(lt)[rang]) end -Base.getindex(lt::LazyTree, ::typeof(!), s::Symbol) = lt[:, s] -Base.getindex(lt::LazyTree, ::Colon, s::Symbol) = getproperty(innertable(lt), s) # the real deal # a specific event +Base.getindex(lt::LazyTree, ::typeof(!), s::Symbol) = lt[:, s] +Base.getindex(lt::LazyTree, ::Colon, s::Symbol) = getproperty(innertable(lt), s) # the real deal Base.getindex(lt::LazyTree, row::Int, col::Symbol) = lt[:, col][row] Base.getindex(lt::LazyTree, rows::UnitRange, col::Symbol) = lt[:, col][rows] Base.getindex(lt::LazyTree, ::Colon) = lt[1:end] @@ -220,6 +227,8 @@ Base.getindex(e::Iterators.Enumerate{LazyTree{T}}, row::Int) where T = (row, fir # interfacing Table Base.names(lt::LazyTree) = collect(String.(propertynames(innertable(lt)))) Base.length(lt::LazyTree) = length(innertable(lt)) +Base.ndims(::Type{<:LazyTree}) = 1 +Base.size(lt::LazyTree) = size(innertable(lt)) function getbranchnamesrecursive(obj) out = Vector{String}() @@ -282,17 +291,15 @@ function LazyTree(f::ROOTFile, s::AbstractString, branch::Union{AbstractString,R return LazyTree(f, s, [branch]) end -struct LazyEvent{T<:TypedTables.Table} - tree::T - idx::Int64 -end function Base.show(io::IO, evt::LazyEvent) idx = Core.getfield(evt, :idx) fields = propertynames(Core.getfield(evt, :tree)) nfields = length(fields) sfields = nfields < 20 ? ": $(fields)" : "" - show(io, "LazyEvent $(idx) with $(nfields) fields$(sfields)") + println(io, "UnROOT.LazyEvent at index $(idx) with $(nfields) columns:") + show(io, collect(evt)) end + function Base.getproperty(evt::LazyEvent, s::Symbol) @inbounds getproperty(Core.getfield(evt, :tree), s)[Core.getfield(evt, :idx)] end @@ -335,5 +342,5 @@ function _clusterbytes(lbs::AbstractVector{<:LazyBranch}; compressed=false) return bytes end -Tables.columns(t::LazyTree) = NamedTuple((p, getproperty(t, p)) for p in propertynames(t)) +Tables.columns(t::LazyTree) = Tables.columns(innertable(t)) Tables.partitions(t::LazyTree) = (t[r] for r in _clusterranges(t)) diff --git a/test/runtests.jl b/test/runtests.jl index 005ab4a7..b97c216c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -368,7 +368,10 @@ end @test length(collect(eachmatch(r"Float32\[", s))) == 0 _io = IOBuffer() show(_io, t) - @test length(split(String(take!(_io)),'\n')) > length(t) + _iostring = String(take!(_io)) + @test length(split(_iostring,'\n')) > length(t) + @test occursin("───────", _iostring) + @test !occursin("NamedTuple", _iostring) show(_io, t; crop=:both) @test length(split(String(take!(_io)),'\n')) <= Base.displaysize()[1] close(f) @@ -689,7 +692,7 @@ end @testset "Basic C++ types" begin f = UnROOT.samplefile("tree_basictypes.root") - onesrow = LazyTree(f,"t")[2] |> values .|> first .|> Int + onesrow = LazyTree(f,"t")[2] |> collect |> values .|> first .|> Int @test all(onesrow .== 1) end @@ -712,3 +715,21 @@ end @test sum(UnROOT._clusterbytes([t.b1]; compressed=true)) == 33493.0 # same as uproot4 @test sum(UnROOT._clusterbytes([t.b2]; compressed=true)) == 23710.0 # same as uproot4 end + + +@static if VERSION > v"1.5.0" + @testset "Broadcast fusion" begin + rootfile = ROOTFile(joinpath(SAMPLES_DIR, "NanoAODv5_sample.root")) + t = LazyTree(rootfile, "Events", "nMuon") + testf(evt) = evt.nMuon == 4 + testf2(evt) = evt.nMuon == 4 + alloc1 = @allocated a1 = testf.(t) + alloc1 += @allocated a2 = testf2.(t) + alloc1 += @allocated idx1 = findall(a1 .& a2) + alloc2 = @allocated idx2 = findall(@. testf(t) & testf2(t)) + @assert !isempty(idx1) + @test idx1 == idx2 + # compiler optimization is good on 1.8 + @test alloc1 > 1.4*alloc2 + end +end From 20ddaa7bbed594df9e5985edd4f3e27f94756189 Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Mon, 4 Oct 2021 18:27:22 -0400 Subject: [PATCH 19/59] Update Project.toml --- Project.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Project.toml b/Project.toml index cd0ac60b..e9dc09b4 100644 --- a/Project.toml +++ b/Project.toml @@ -29,6 +29,7 @@ CodecLz4 = "^0.3.0, ^0.4.0" CodecXz = "^0.6.0, ^0.7.0" CodecZlib = "^0.6.0, ^0.7.0" CodecZstd = "^0.6.0, ^0.7.0" +IterTools = "^1" LRUCache = "^1.3.0" LorentzVectors = "^0.4.0" Memoization = "^0.1.10" From 776eee714f3c96606e2e166e6e7943ae4275c221 Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Mon, 4 Oct 2021 19:48:24 -0700 Subject: [PATCH 20/59] avoid intermediate array for lz4/zlib (#128) --- src/types.jl | 52 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/src/types.jl b/src/types.jl index 613fa5f8..1526d179 100644 --- a/src/types.jl +++ b/src/types.jl @@ -127,6 +127,31 @@ function compressed_datastream(io, tkey) return read(io, tkey.fNbytes - tkey.fKeylen) end +function _decompress_zlib!(input_ptr, input_size, output_ptr, output_size) + # References: + # https://github.com/JuliaIO/CodecZlib.jl/blob/a777d8f53aebd223fe7c7399436a5050784d210f/src/libz.jl + # https://github.com/root-project/root/blob/87a998d48803bc207288d90038e60ff148827664/core/zip/src/RZip.cxx#L392 + zstream = CodecZlib.ZStream() + zstream.next_in = input_ptr + zstream.avail_in = input_size + zstream.next_out = output_ptr + zstream.avail_out = output_size + CodecZlib.inflate_init!(zstream, CodecZlib.Z_DEFAULT_WINDOWBITS) + while (err = CodecZlib.inflate!(zstream, CodecZlib.Z_FINISH) != CodecZlib.Z_STREAM_END) + if (err != CodecZlib.Z_OK) + CodecZlib.inflate_end!(zstream) + error(CodecZlib.zlib_error_message(zstream, err)) + end + end + CodecZlib.inflate_end!(zstream) + nothing +end + +function _decompress_lz4!(input_ptr, input_size, output_ptr, output_size) + CodecLz4.LZ4_decompress_safe(input_ptr, output_ptr, input_size, output_size) + nothing +end + """ decompress_datastreambytes(compbytes, tkey) @@ -147,19 +172,30 @@ function decompress_datastreambytes(compbytes, tkey) cname, _, compbytes, uncompbytes = unpack(compression_header) rawbytes = read(io, compbytes) - # indexing `0+1 to 0+2` are two bytes, no need to +1 in the second term - @view(uncomp_data[fufilled+1:fufilled+uncompbytes]) .= if cname == "ZL" - transcode(ZlibDecompressor, rawbytes) + if cname == "L4" + # skip checksum which is 8 bytes + # original: lz4_decompress(rawbytes[9:end], uncompbytes) + input = @view rawbytes[9:end] + input_ptr = pointer(input) + input_size = length(input) + output_ptr = pointer(uncomp_data) + fufilled + output_size = uncompbytes + _decompress_lz4!(input_ptr, input_size, output_ptr, output_size) + elseif cname == "ZL" + # original: @view(uncomp_data[fufilled+1:fufilled+uncompbytes]) .= transcode(ZlibDecompressor, rawbytes) + input_ptr = pointer(rawbytes) + input_size = length(rawbytes) + output_ptr = pointer(uncomp_data) + fufilled + output_size = uncompbytes + _decompress_zlib!(input_ptr, input_size, output_ptr, output_size) elseif cname == "XZ" - transcode(XzDecompressor, rawbytes) + @view(uncomp_data[fufilled+1:fufilled+uncompbytes]) .= transcode(XzDecompressor, rawbytes) elseif cname == "ZS" - transcode(ZstdDecompressor, rawbytes) - elseif cname == "L4" - # skip checksum which is 8 bytes - lz4_decompress(rawbytes[9:end], uncompbytes) + @view(uncomp_data[fufilled+1:fufilled+uncompbytes]) .= transcode(ZstdDecompressor, rawbytes) else error("Unsupported compression type '$(String(compression_header.algo))'") end + fufilled += uncompbytes end return uncomp_data From 961b1d1319e2c4a0534c10fe360465b1d6148b91 Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Tue, 5 Oct 2021 05:06:39 -0700 Subject: [PATCH 21/59] test zstd compression (#129) * add zstd compression tests --- src/streamers.jl | 2 +- test/runtests.jl | 5 +++++ test/samples/tree_with_int_array_zstd.root | Bin 0 -> 4929 bytes test/samples/zstd_tree.py | 18 ++++++++++++++++++ 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 test/samples/tree_with_int_array_zstd.root create mode 100755 test/samples/zstd_tree.py diff --git a/src/streamers.jl b/src/streamers.jl index 4b14ffb3..e0f869f9 100644 --- a/src/streamers.jl +++ b/src/streamers.jl @@ -87,7 +87,7 @@ function Streamers(io) elseif cname == "XZ" IOBuffer(transcode(XzDecompressor, compressedbytes)) elseif cname == "ZS" - IOBuffer(transcode(ZstdDecompressor, io)) + IOBuffer(transcode(ZstdDecompressor, compressedbytes)) elseif cname == "L4" IOBuffer(lz4_decompress(compressedbytes[9:end], tkey.fObjlen)) else diff --git a/test/runtests.jl b/test/runtests.jl index b97c216c..2a922a83 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -183,6 +183,11 @@ end @test 100000 == length(arr) @test [0.0, 1.0588236, 2.1176472, 3.1764705, 4.2352943] ≈ arr[1:5] atol=1e-7 close(rootfile) + + rootfile = ROOTFile(joinpath(SAMPLES_DIR, "tree_with_int_array_zstd.root")) + arr = collect(rootfile["t1/a"]) + @test arr == 0:99 + close(rootfile) end @testset "ROOTDirectoryHeader" begin diff --git a/test/samples/tree_with_int_array_zstd.root b/test/samples/tree_with_int_array_zstd.root new file mode 100644 index 0000000000000000000000000000000000000000..82cbfcaa4edc45b006a505ba933d26c1fcf0fb86 GIT binary patch literal 4929 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 Date: Tue, 5 Oct 2021 08:06:48 -0400 Subject: [PATCH 22/59] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index e9dc09b4..2ffff512 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnROOT" uuid = "3cd96dde-e98d-4713-81e9-a4a1b0235ce9" authors = ["Tamas Gal", "Jerry Ling", "Johannes Schumann", "Nick Amin"] -version = "0.7.0" +version = "0.7.1" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 4a284bf032c46a92f497e38c7d3a8971087fa2e4 Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Thu, 7 Oct 2021 12:41:59 -0700 Subject: [PATCH 23/59] custom html repr (#132) * custom html repr * limit columns too * consolidate text * reduce clutter Co-authored-by: Jerry Ling * Update src/displays.jl --- src/displays.jl | 36 +++++++++++++++++++++++++++++++++++- test/runtests.jl | 4 ++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/displays.jl b/src/displays.jl index 941b3b6d..6c82e270 100644 --- a/src/displays.jl +++ b/src/displays.jl @@ -77,6 +77,39 @@ function _show(io::IO, tree::LazyTree; kwargs...) ) nothing end +function Base.show(io::IO, ::MIME"text/html", tree::LazyTree) + _hs = _make_header(tree) + maxrows = 10 + maxcols = 30 + nrow = length(tree) + t = @view innertable(tree)[1:min(maxrows,nrow)] + ncol = length(Tables.columns(t)) + withcommas(value) = reverse(join(join.(Iterators.partition(reverse(string(value)),3)),",")) + write(io, "

") + write(io, "$(withcommas(nrow)) rows × $(ncol) columns") + if (nrow > maxrows) && (ncol > maxcols) + write(io, " (omitted printing of $(withcommas(nrow-maxrows)) rows and $(ncol-maxcols) columns)") + elseif (nrow > maxrows) + write(io, " (omitted printing of $(withcommas(nrow-maxrows)) rows)") + elseif (ncol > maxcols) + write(io, " (omitted printing of $(ncol-maxcols) columns)") + end + write(io, "

") + PrettyTables.pretty_table( + io, + t; + header=_hs, + alignment=:l, + row_number_column_title="", + show_row_number=true, + compact_printing=false, + filters_col = ((_,i) -> i <= maxcols,), + formatters=(v, i, j) -> _treeformat(v, 100), + tf = PrettyTables.HTMLTableFormat(css = """th { color: #000; background-color: #fff; }"""), + backend=Val(:html), + ) + nothing +end _symtup2str(symtup, trunc=15) = collect(first.(string.(symtup), trunc)) function _make_header(t) pn = propertynames(t) @@ -86,7 +119,8 @@ function _make_header(t) end function _treeformat(val, trunc) s = if val isa AbstractArray{T} where T<:Integer - string(Int.(val)) + T = Int + replace(string(T.(val)), string(T)=>"") elseif val isa AbstractArray{T} where T<:AbstractFloat T = eltype(val) replace(string(round.(T.(val); sigdigits=3)), string(T)=>"") diff --git a/test/runtests.jl b/test/runtests.jl index 2a922a83..406d9bc0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -379,6 +379,10 @@ end @test !occursin("NamedTuple", _iostring) show(_io, t; crop=:both) @test length(split(String(take!(_io)),'\n')) <= Base.displaysize()[1] + _io = IOBuffer() + show(_io, MIME"text/html"(), t) + _iostring = String(take!(_io)) + @test occursin("", _iostring) close(f) end From 0d1ed21d42ce64e7dfaf6836585bc57d205f312b Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Thu, 7 Oct 2021 21:02:45 -0400 Subject: [PATCH 24/59] add lazy Chain/Vcat for Tree (#131) --- Project.toml | 5 ++++- src/UnROOT.jl | 2 +- src/iteration.jl | 19 +++++++++++++++++-- test/runtests.jl | 29 ++++++++++++++++++++++++++--- 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/Project.toml b/Project.toml index 2ffff512..2e644fc2 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnROOT" uuid = "3cd96dde-e98d-4713-81e9-a4a1b0235ce9" authors = ["Tamas Gal", "Jerry Ling", "Johannes Schumann", "Nick Amin"] -version = "0.7.1" +version = "0.7.2" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" @@ -12,12 +12,14 @@ CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193" CodecZstd = "6b39b394-51ab-5f42-8807-6242bab2b4c2" IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e" LRUCache = "8ac3fa9e-de4c-5943-b1dc-09c6b5f20637" +LazyArrays = "5078a376-72f3-5289-bfd5-ec5146d43c02" LorentzVectors = "3f54b04b-17fc-5cd4-9758-90c048d965e3" Memoization = "6fafb56a-5788-4b4e-91ca-c0cea6611c73" Mixers = "2a8e4939-dab8-5edc-8f64-72a8776f13de" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" Requires = "ae029012-a4dd-5104-9daa-d747884805df" +SentinelArrays = "91c51154-3ec4-41a3-a24f-3f23e20d615c" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" TypedTables = "9d95f2ec-7b3d-5a63-8d20-e2491e220bb9" @@ -31,6 +33,7 @@ CodecZlib = "^0.6.0, ^0.7.0" CodecZstd = "^0.6.0, ^0.7.0" IterTools = "^1" LRUCache = "^1.3.0" +LazyArrays = "^0.22, ^1" LorentzVectors = "^0.4.0" Memoization = "^0.1.10" Mixers = "^0.1.0" diff --git a/src/UnROOT.jl b/src/UnROOT.jl index 9c08e5cb..18b40412 100644 --- a/src/UnROOT.jl +++ b/src/UnROOT.jl @@ -1,6 +1,6 @@ module UnROOT -using Requires +using Requires, LazyArrays export ROOTFile, LazyBranch, LazyTree, @batch import Base: close, keys, get, getindex, getproperty, show, length, iterate, position, ntoh, lock, unlock, reinterpret diff --git a/src/iteration.jl b/src/iteration.jl index d009c93b..ff2b83a3 100644 --- a/src/iteration.jl +++ b/src/iteration.jl @@ -109,7 +109,13 @@ mutable struct LazyBranch{T,J,B} <: AbstractVector{T} function LazyBranch(f::ROOTFile, b::Union{TBranch,TBranchElement}) T, J = auto_T_JaggT(f, b; customstructs=f.customstructs) T = (T === Vector{Bool} ? BitVector : T) - _buffer = J === Nojagg ? T[] : VectorOfVectors(T(), Int32[1]) + _buffer = T[] + if J != Nojagg + # if branch is jagged, fix the buffer and eltype according to what + # VectorOfVectors would return in `getindex` + _buffer = VectorOfVectors(T(), Int32[1]) + T = SubArray{eltype(T), 1, T, Tuple{UnitRange{Int64}}, true} + end return new{T,J,typeof(_buffer)}(f, b, length(b), b.fBasketEntry, [_buffer for _ in 1:Threads.nthreads()], @@ -230,6 +236,15 @@ Base.length(lt::LazyTree) = length(innertable(lt)) Base.ndims(::Type{<:LazyTree}) = 1 Base.size(lt::LazyTree) = size(innertable(lt)) +function LazyArrays.Vcat(ts::LazyTree...) + cs = Tables.columns.(innertable.(ts)) + LazyTree(TypedTables.Table(map(Vcat, cs...))) +end +Base.vcat(ts::LazyTree...) = Vcat(ts...) +Base.reduce(::typeof(vcat), ts::AbstractVector{<:LazyTree}) = Vcat((ts)...) +Base.mapreduce(f::Function, ::typeof(vcat), ts::AbstractVector{<:LazyTree}) = Vcat(f.(ts)...) +Base.mapreduce(f::Function, ::typeof(Vcat), ts::AbstractVector{<:LazyTree}) = Vcat(f.(ts)...) + function getbranchnamesrecursive(obj) out = Vector{String}() for b in obj.fBranches.elements @@ -300,7 +315,7 @@ function Base.show(io::IO, evt::LazyEvent) show(io, collect(evt)) end -function Base.getproperty(evt::LazyEvent, s::Symbol) +@inline function Base.getproperty(evt::LazyEvent, s::Symbol) @inbounds getproperty(Core.getfield(evt, :tree), s)[Core.getfield(evt, :idx)] end Base.collect(evt::LazyEvent) = @inbounds Core.getfield(evt, :tree)[Core.getfield(evt, :idx)] diff --git a/test/runtests.jl b/test/runtests.jl index 406d9bc0..4a9c33dd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -263,7 +263,8 @@ end branch = rootfile["t1/LVs"] tree = LazyTree(rootfile, "t1") - @test eltype(branch) === Vector{LorentzVectors.LorentzVector{Float64}} + @test eltype(branch) <: AbstractVector{LorentzVectors.LorentzVector{Float64}} + @test eltype(branch) <: SubArray @test length.(branch[1:10]) == 0:9 close(rootfile) end @@ -292,7 +293,7 @@ end rootfile = ROOTFile(joinpath(SAMPLES_DIR, "tree_with_jagged_array_double.root")) data = rootfile["t1/double_array"] @test data isa AbstractVector - @test eltype(data) === Vector{T} + @test eltype(data) <: AbstractVector{T} @test data[1] == T[] @test data[1:2] == [T[], T[0]] @test data[end] == T[90, 91, 92, 93, 94, 95, 96, 97, 98] @@ -317,7 +318,7 @@ end event = UnROOT.array(rootfile, "Events/event") @test event[1:3] == UInt64[12423832, 12423821, 12423834] Electron_dxy = rootfile["Events/Electron_dxy"] - @test eltype(Electron_dxy) == Vector{Float32} + @test eltype(Electron_dxy) == SubArray{Float32, 1, Vector{Float32}, Tuple{UnitRange{Int64}}, true} @test Electron_dxy[1:3] ≈ [Float32[0.0003705], Float32[-0.00981903], Float32[]] HLT_Mu3_PFJet40 = UnROOT.array(rootfile, "Events/HLT_Mu3_PFJet40") @test eltype(HLT_Mu3_PFJet40) == Bool @@ -677,6 +678,14 @@ end end @test count(>(0), nmus) > 1 # test @batch is actually threading @test sum(nmus) == 878 + + nmus .= 0 + t_dummy = LazyTree(ROOTFile(joinpath(SAMPLES_DIR, "NanoAODv5_sample.root")), "Events", ["Muon_pt"]) + @batch for evt in vcat(t,t_dummy) # avoid using the same underlying file handler + nmus[Threads.threadid()] += length(evt.Muon_pt) + end + @test sum(nmus) == 2*878 + for j in 1:3 inds = [Vector{Int}() for _ in 1:Threads.nthreads()] @batch for (i, evt) in enumerate(t) @@ -725,6 +734,20 @@ end @test sum(UnROOT._clusterbytes([t.b2]; compressed=true)) == 23710.0 # same as uproot4 end +@testset "Vcat/chaining" begin + rootfile = ROOTFile(joinpath(SAMPLES_DIR, "NanoAODv5_sample.root")) + t = LazyTree(rootfile, "Events", ["nMuon", "Muon_pt"]) + tt = vcat(t,t) + @test (@allocated vcat(t,t)) < 1000 + @test length(tt) == 2*length(t) + s1 = sum(t.nMuon) + s2 = sum(tt.nMuon) + @test s2 == 2*s1 + alloc1 = @allocated sum(length, t.Muon_pt) + alloc2 = @allocated sum(evt->length(evt.nMuon), tt) + @test alloc2 < 2.1 * alloc1 + close(rootfile) +end @static if VERSION > v"1.5.0" @testset "Broadcast fusion" begin From 0409b7c9560d34b20553b98834d128ce725a3be6 Mon Sep 17 00:00:00 2001 From: Moelf Date: Thu, 7 Oct 2021 21:03:42 -0400 Subject: [PATCH 25/59] remove unused --- Project.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Project.toml b/Project.toml index 2e644fc2..1db6d673 100644 --- a/Project.toml +++ b/Project.toml @@ -19,7 +19,6 @@ Mixers = "2a8e4939-dab8-5edc-8f64-72a8776f13de" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" Requires = "ae029012-a4dd-5104-9daa-d747884805df" -SentinelArrays = "91c51154-3ec4-41a3-a24f-3f23e20d615c" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" TypedTables = "9d95f2ec-7b3d-5a63-8d20-e2491e220bb9" From 8c2c452ec7017559b00ff6789a7b338f3a553bd7 Mon Sep 17 00:00:00 2001 From: Moelf Date: Thu, 7 Oct 2021 21:07:49 -0400 Subject: [PATCH 26/59] fix compat --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 1db6d673..f94db293 100644 --- a/Project.toml +++ b/Project.toml @@ -32,7 +32,7 @@ CodecZlib = "^0.6.0, ^0.7.0" CodecZstd = "^0.6.0, ^0.7.0" IterTools = "^1" LRUCache = "^1.3.0" -LazyArrays = "^0.22, ^1" +LazyArrays = "^0.15, ^0.21, ^0.22, ^1" LorentzVectors = "^0.4.0" Memoization = "^0.1.10" Mixers = "^0.1.0" From 4d526eec11ed44fa4bbe4582bd0199a8f616b826 Mon Sep 17 00:00:00 2001 From: Moelf Date: Thu, 7 Oct 2021 22:46:56 -0400 Subject: [PATCH 27/59] remove ill-defined method --- src/bootstrap.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/bootstrap.jl b/src/bootstrap.jl index dbe24b66..92ef2ea8 100644 --- a/src/bootstrap.jl +++ b/src/bootstrap.jl @@ -419,10 +419,6 @@ end Base.length(b::Union{TBranch, TBranchElement}) = b.fEntries Base.eachindex(b::Union{TBranch, TBranchElement}) = Base.OneTo(b.fEntries) numbaskets(b::Union{TBranch, TBranchElement}) = findfirst(x->x>(b.fEntries-1),b.fBasketEntry)-1 -function Base.eltype(b::Union{TBranch, TBranchElement}) - T, jagT = interp_jaggT(b) - jagT === Nojagg ? T : Vector{T} -end @with_kw struct TBranch_12 <: TBranch cursor::Cursor From ef0bae839ffe8cb7747f36cf5b727a6278cef5ab Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Thu, 7 Oct 2021 23:52:02 -0400 Subject: [PATCH 28/59] performance bug fix from Polyester --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index f94db293..822be732 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnROOT" uuid = "3cd96dde-e98d-4713-81e9-a4a1b0235ce9" authors = ["Tamas Gal", "Jerry Ling", "Johannes Schumann", "Nick Amin"] -version = "0.7.2" +version = "0.7.3" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" @@ -37,7 +37,7 @@ LorentzVectors = "^0.4.0" Memoization = "^0.1.10" Mixers = "^0.1.0" Parameters = "^0.12.0" -Polyester = "^0.4.2" +Polyester = "^0.5.3" PrettyTables = "^1.2.0" Requires = "^1" StaticArrays = "^0.12.0, ^1" From 934b8dcaa02eb78a0a4670476c7ad2fb12518507 Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Mon, 11 Oct 2021 21:36:06 -0400 Subject: [PATCH 29/59] remove Polyester (#134) --- Project.toml | 4 +--- README.md | 4 +--- docs/src/index.md | 19 +++++++------------ docs/src/performancetips.md | 24 ------------------------ src/UnROOT.jl | 8 ++------ src/iteration.jl | 4 ++-- src/polyester.jl | 8 -------- test/runtests.jl | 15 +-------------- 8 files changed, 14 insertions(+), 72 deletions(-) delete mode 100644 src/polyester.jl diff --git a/Project.toml b/Project.toml index 822be732..e6524dbd 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnROOT" uuid = "3cd96dde-e98d-4713-81e9-a4a1b0235ce9" authors = ["Tamas Gal", "Jerry Ling", "Johannes Schumann", "Nick Amin"] -version = "0.7.3" +version = "0.8.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" @@ -18,7 +18,6 @@ Memoization = "6fafb56a-5788-4b4e-91ca-c0cea6611c73" Mixers = "2a8e4939-dab8-5edc-8f64-72a8776f13de" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" -Requires = "ae029012-a4dd-5104-9daa-d747884805df" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" TypedTables = "9d95f2ec-7b3d-5a63-8d20-e2491e220bb9" @@ -39,7 +38,6 @@ Mixers = "^0.1.0" Parameters = "^0.12.0" Polyester = "^0.5.3" PrettyTables = "^1.2.0" -Requires = "^1" StaticArrays = "^0.12.0, ^1" Tables = "^1.0.0" TypedTables = "^1.0.0" diff --git a/README.md b/README.md index cb662c03..b81f02c3 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,7 @@ julia> for event in mytree end event.Electron_dxy = Float32[0.00037050247] -julia> using Polyester #optional dependency - -julia> @batch for event in mytree # multi-threading +julia> Threads.@threads for event in mytree # multi-threading ... end ``` diff --git a/docs/src/index.md b/docs/src/index.md index 2623912f..3d57fb4b 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -33,23 +33,18 @@ julia> for (i, event) in enumerate(mytree) end ``` -Both of which are compostable with `@batch` from `Polyester.jl` for multi-threading: +Both of which are compostable with `@threads` for multi-threading: ```julia -julia> using Polyester # need to install it first as it's an optional dependency - -julia> @batch for event in mytree +julia> Threads.@threads for event in mytree ... end -julia> @batch for (i, event) in enumerate(mytree) +julia> Threads.@threads for (i, event) in enumerate(mytree) ... end ``` -On finer control over `@batch`, such as batch size or per-core/thread, see [Polyester](https://github.com/JuliaSIMD/Polyester.jl)'s page. - Only one basket per branch will be cached so you don't have to worry about running out of RAM. -At the same time, `event` inside the for-loop is not materialized until a field is accessed. If your event -is fairly small or you need all of them anyway, you can `collect(event)` first inside the loop. +At the same time, `event` inside the for-loop is not materialized until a field is accessed. ## Laziness in Indexing, Slicing, and Looping Laziness (or eagerness) in UnROOT generally refers to if an "event" has read each branches of the tree or not. @@ -84,6 +79,6 @@ The laziness of the main interfaces are summarized below: | | `mytree` | `enumerate(mytree)` | | ---------------------- |:-----------:|:-------------------:| | `for X in ...` | 💤 | 💤 | -| `@threads for X in ...`| 🚨 | 💤 | -| `@batch for X in ...` | 💤 | 💤 | -| `getindex()` | 🚨 | 💤 | +| `@threads for X in ...`| 💤 | 💤 | +| `getindex(tree, row::Int)`| 💤 | N/A | +| `getindex(tree, row::Range)`| 🚨 | N/A | diff --git a/docs/src/performancetips.md b/docs/src/performancetips.md index 59ca1f95..555c3edb 100644 --- a/docs/src/performancetips.md +++ b/docs/src/performancetips.md @@ -15,27 +15,3 @@ for evt in mytree calculation(nmu) end ``` - -## `Threads.@threads` should go with `enumerate()` -tl;dr: just use `@batch` provided by [Polyester.jl](https://github.com/JuliaSIMD/Polyester.jl) since UnROOT -can customize behavior. - -Unlike `@batch`, there's not much we can do to customize behavior of `@threads`. It is essentially -calling `getindex()`, which we want to keep eager for regular use (e.v `mytree[120]` is eager). Thus, if for some -reason you want to use `@threads` instead of `@batch`, you should use it with `enumerate`: -```julia -julia> for evt in mytree - @show evt - break - end -evt = "LazyEvent with: (:tree, :idx)" - -julia> Threads.@threads for evt in mytree - @show evt - break - end -evt = (nMuon = 0x00000000, Muon_pt = Float32[]) -evt = (nMuon = 0x00000001, Muon_pt = Float32[3.4505641]) -evt = (nMuon = 0x00000000, Muon_pt = Float32[]) -evt = (nMuon = 0x00000002, Muon_pt = Float32[21.279676, 7.6710315]) -``` diff --git a/src/UnROOT.jl b/src/UnROOT.jl index 18b40412..04b1de76 100644 --- a/src/UnROOT.jl +++ b/src/UnROOT.jl @@ -1,7 +1,7 @@ module UnROOT -using Requires, LazyArrays -export ROOTFile, LazyBranch, LazyTree, @batch +using LazyArrays +export ROOTFile, LazyBranch, LazyTree import Base: close, keys, get, getindex, getproperty, show, length, iterate, position, ntoh, lock, unlock, reinterpret ntoh(b::Bool) = b @@ -30,8 +30,4 @@ include("iteration.jl") include("custom.jl") include("displays.jl") -function __init__() - @require Polyester="f517fe37-dbe3-4b94-8317-1923a5111588" include("polyester.jl") -end - end # module diff --git a/src/iteration.jl b/src/iteration.jl index ff2b83a3..f70eccb7 100644 --- a/src/iteration.jl +++ b/src/iteration.jl @@ -225,11 +225,11 @@ Base.lastindex(lt::LazyTree) = length(lt) Base.eachindex(lt::LazyTree) = 1:lastindex(lt) # allow enumerate() to be chunkable (eg with Threads.@threads) +Base.step(e::Iterators.Enumerate{LazyTree{T}}) where T = 1 Base.firstindex(e::Iterators.Enumerate{LazyTree{T}}) where T = firstindex(e.itr) Base.lastindex(e::Iterators.Enumerate{LazyTree{T}}) where T = lastindex(e.itr) Base.eachindex(e::Iterators.Enumerate{LazyTree{T}}) where T = eachindex(e.itr) -Base.getindex(e::Iterators.Enumerate{LazyTree{T}}, row::Int) where T = (row, first(iterate(e.itr, row))) - +Base.getindex(e::Iterators.Enumerate{LazyTree{T}}, row::Int) where T = (row, LazyEvent(innertable(e.itr), row)) # interfacing Table Base.names(lt::LazyTree) = collect(String.(propertynames(innertable(lt)))) Base.length(lt::LazyTree) = length(innertable(lt)) diff --git a/src/polyester.jl b/src/polyester.jl deleted file mode 100644 index 3f4b0b07..00000000 --- a/src/polyester.jl +++ /dev/null @@ -1,8 +0,0 @@ -# special Requires.jl syntax -import .Polyester: splitloop, combine, NoLoop - -splitloop(t::LazyTree) = NoLoop(), eachindex(t), t -combine(t::LazyTree, ::NoLoop, j) = LazyEvent(innertable(t), j) - -splitloop(e::Base.Iterators.Enumerate{LazyTree{T}}) where T = NoLoop(), eachindex(e.itr), e -combine(e::Iterators.Enumerate{LazyTree{T}}, ::NoLoop, j) where T = @inbounds e[j] diff --git a/test/runtests.jl b/test/runtests.jl index 4a9c33dd..6854bab4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -658,19 +658,6 @@ end @test count(>(0), nmus) > 1 # test @threads is actually threading @test sum(nmus) == 878 - nmus .= 0 - @batch for (i, evt) in enumerate(t) - nmus[Threads.threadid()] += length(evt.Muon_pt) - end - @test count(>(0), nmus) > 1 # test @batch is actually threading - @test sum(nmus) == 878 - - event_nums = zeros(Int, Threads.nthreads()) - @batch for (i, evt) in enumerate(t) - event_nums[Threads.threadid()] += 1 - end - @test count(>(0), event_nums) > 1 # test @batch is actually threading - @test sum(event_nums) == length(t) nmus .= 0 @batch for evt in t @@ -688,7 +675,7 @@ end for j in 1:3 inds = [Vector{Int}() for _ in 1:Threads.nthreads()] - @batch for (i, evt) in enumerate(t) + Threads.@threads for (i, evt) in enumerate(t) push!(inds[Threads.threadid()], i) end @test sum([length(inds[i] ∩ inds[j]) for i=1:length(inds), j=1:length(inds) if j>i]) == 0 From 3cf9cd59e72f4b94f84f69a84f801d5722504b66 Mon Sep 17 00:00:00 2001 From: Moelf Date: Wed, 20 Oct 2021 23:05:36 -0400 Subject: [PATCH 30/59] add LazyTree(path) methods --- src/iteration.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/iteration.jl b/src/iteration.jl index f70eccb7..e5fb5267 100644 --- a/src/iteration.jl +++ b/src/iteration.jl @@ -199,6 +199,9 @@ end struct LazyTree{T} <: AbstractVector{LazyEvent{T}} treetable::T end +function LazyTree(path::String, x...) + LazyTree(ROOTFile(path), x...) +end @inline innertable(t::LazyTree) = Core.getfield(t, :treetable) From 972e71da810ad0018d387a67292ff72a88de1b32 Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Sat, 27 Nov 2021 10:38:13 -0600 Subject: [PATCH 31/59] use LibDeflate for zlib (#137) --- Project.toml | 4 ++-- src/UnROOT.jl | 4 +++- src/streamers.jl | 4 +++- src/types.jl | 41 +++++++++++++++++------------------------ 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/Project.toml b/Project.toml index e6524dbd..250cb20b 100644 --- a/Project.toml +++ b/Project.toml @@ -8,11 +8,11 @@ AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" ArraysOfArrays = "65a8f2f4-9b39-5baf-92e2-a9cc46fdf018" CodecLz4 = "5ba52731-8f18-5e0d-9241-30f10d1ec561" CodecXz = "ba30903b-d9e8-5048-a5ec-d1f5b0d4b47b" -CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193" CodecZstd = "6b39b394-51ab-5f42-8807-6242bab2b4c2" IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e" LRUCache = "8ac3fa9e-de4c-5943-b1dc-09c6b5f20637" LazyArrays = "5078a376-72f3-5289-bfd5-ec5146d43c02" +LibDeflate = "9255714d-24a7-4b30-8ea3-d46a97f7e13b" LorentzVectors = "3f54b04b-17fc-5cd4-9758-90c048d965e3" Memoization = "6fafb56a-5788-4b4e-91ca-c0cea6611c73" Mixers = "2a8e4939-dab8-5edc-8f64-72a8776f13de" @@ -27,11 +27,11 @@ AbstractTrees = "^0.3.0" ArraysOfArrays = "^0.5.3" CodecLz4 = "^0.3.0, ^0.4.0" CodecXz = "^0.6.0, ^0.7.0" -CodecZlib = "^0.6.0, ^0.7.0" CodecZstd = "^0.6.0, ^0.7.0" IterTools = "^1" LRUCache = "^1.3.0" LazyArrays = "^0.15, ^0.21, ^0.22, ^1" +LibDeflate = "^0.4.0" LorentzVectors = "^0.4.0" Memoization = "^0.1.10" Mixers = "^0.1.0" diff --git a/src/UnROOT.jl b/src/UnROOT.jl index 04b1de76..dc2cf173 100644 --- a/src/UnROOT.jl +++ b/src/UnROOT.jl @@ -8,10 +8,12 @@ ntoh(b::Bool) = b import AbstractTrees: children, printnode, print_tree -using CodecZlib, CodecLz4, CodecXz, CodecZstd, StaticArrays, LorentzVectors, ArraysOfArrays +using CodecLz4, CodecXz, CodecZstd, StaticArrays, LorentzVectors, ArraysOfArrays using Mixers, Parameters, Memoization, LRUCache import IterTools: groupby +import LibDeflate: unsafe_zlib_decompress!, Decompressor + import Tables, TypedTables, PrettyTables @static if VERSION < v"1.6" diff --git a/src/streamers.jl b/src/streamers.jl index e0f869f9..5a1e30a9 100644 --- a/src/streamers.jl +++ b/src/streamers.jl @@ -83,7 +83,9 @@ function Streamers(io) cname = String(compression_header.algo) if cname == "ZL" - IOBuffer(transcode(ZlibDecompressor, compressedbytes)) + output = Vector{UInt8}(undef, tkey.fObjlen) + _decompress_zlib!(output, compressedbytes, length(output)) + IOBuffer(output) elseif cname == "XZ" IOBuffer(transcode(XzDecompressor, compressedbytes)) elseif cname == "ZS" diff --git a/src/types.jl b/src/types.jl index 1526d179..cc41add8 100644 --- a/src/types.jl +++ b/src/types.jl @@ -127,24 +127,21 @@ function compressed_datastream(io, tkey) return read(io, tkey.fNbytes - tkey.fKeylen) end -function _decompress_zlib!(input_ptr, input_size, output_ptr, output_size) - # References: - # https://github.com/JuliaIO/CodecZlib.jl/blob/a777d8f53aebd223fe7c7399436a5050784d210f/src/libz.jl - # https://github.com/root-project/root/blob/87a998d48803bc207288d90038e60ff148827664/core/zip/src/RZip.cxx#L392 - zstream = CodecZlib.ZStream() - zstream.next_in = input_ptr - zstream.avail_in = input_size - zstream.next_out = output_ptr - zstream.avail_out = output_size - CodecZlib.inflate_init!(zstream, CodecZlib.Z_DEFAULT_WINDOWBITS) - while (err = CodecZlib.inflate!(zstream, CodecZlib.Z_FINISH) != CodecZlib.Z_STREAM_END) - if (err != CodecZlib.Z_OK) - CodecZlib.inflate_end!(zstream) - error(CodecZlib.zlib_error_message(zstream, err)) - end - end - CodecZlib.inflate_end!(zstream) - nothing +# Based on +# https://github.com/jakobnissen/LibDeflate.jl/blob/d172c4429404a6ce73b3d44541e80d6f69a0f38a/src/zlib.jl#L38 +function _decompress_zlib!( + output::AbstractArray{UInt8}, + input, + n_out::Integer +) + GC.@preserve output input unsafe_zlib_decompress!( + Base.HasLength(), + Decompressor(), + pointer(output), + n_out, + pointer(input), + sizeof(input) + ) end function _decompress_lz4!(input_ptr, input_size, output_ptr, output_size) @@ -182,12 +179,8 @@ function decompress_datastreambytes(compbytes, tkey) output_size = uncompbytes _decompress_lz4!(input_ptr, input_size, output_ptr, output_size) elseif cname == "ZL" - # original: @view(uncomp_data[fufilled+1:fufilled+uncompbytes]) .= transcode(ZlibDecompressor, rawbytes) - input_ptr = pointer(rawbytes) - input_size = length(rawbytes) - output_ptr = pointer(uncomp_data) + fufilled - output_size = uncompbytes - _decompress_zlib!(input_ptr, input_size, output_ptr, output_size) + output = @view(uncomp_data[fufilled+1:fufilled+uncompbytes]) + _decompress_zlib!(output, rawbytes, uncompbytes) elseif cname == "XZ" @view(uncomp_data[fufilled+1:fufilled+uncompbytes]) .= transcode(XzDecompressor, rawbytes) elseif cname == "ZS" From 29e206fdd9d5d72a956033767f4539919316e86f Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Sat, 27 Nov 2021 11:38:35 -0500 Subject: [PATCH 32/59] Update Project.toml LibDeflate fast! --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 250cb20b..02cf07eb 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnROOT" uuid = "3cd96dde-e98d-4713-81e9-a4a1b0235ce9" authors = ["Tamas Gal", "Jerry Ling", "Johannes Schumann", "Nick Amin"] -version = "0.8.0" +version = "0.8.1" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From fe44d2b46fb5fc91b0e52a0a988df8e23df636a0 Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Sat, 27 Nov 2021 18:19:31 -0600 Subject: [PATCH 33/59] use safe api for LibDeflate (#138) --- Project.toml | 2 +- src/UnROOT.jl | 2 +- src/streamers.jl | 2 +- src/types.jl | 19 +------------------ 4 files changed, 4 insertions(+), 21 deletions(-) diff --git a/Project.toml b/Project.toml index 02cf07eb..b2c4e8b6 100644 --- a/Project.toml +++ b/Project.toml @@ -31,7 +31,7 @@ CodecZstd = "^0.6.0, ^0.7.0" IterTools = "^1" LRUCache = "^1.3.0" LazyArrays = "^0.15, ^0.21, ^0.22, ^1" -LibDeflate = "^0.4.0" +LibDeflate = "^0.4.1" LorentzVectors = "^0.4.0" Memoization = "^0.1.10" Mixers = "^0.1.0" diff --git a/src/UnROOT.jl b/src/UnROOT.jl index dc2cf173..17c966b8 100644 --- a/src/UnROOT.jl +++ b/src/UnROOT.jl @@ -12,7 +12,7 @@ using CodecLz4, CodecXz, CodecZstd, StaticArrays, LorentzVectors, ArraysOfArrays using Mixers, Parameters, Memoization, LRUCache import IterTools: groupby -import LibDeflate: unsafe_zlib_decompress!, Decompressor +import LibDeflate: zlib_decompress!, Decompressor import Tables, TypedTables, PrettyTables diff --git a/src/streamers.jl b/src/streamers.jl index 5a1e30a9..3449acbf 100644 --- a/src/streamers.jl +++ b/src/streamers.jl @@ -84,7 +84,7 @@ function Streamers(io) if cname == "ZL" output = Vector{UInt8}(undef, tkey.fObjlen) - _decompress_zlib!(output, compressedbytes, length(output)) + zlib_decompress!(Decompressor(), output, compressedbytes, length(output)) IOBuffer(output) elseif cname == "XZ" IOBuffer(transcode(XzDecompressor, compressedbytes)) diff --git a/src/types.jl b/src/types.jl index cc41add8..ce0ac1ff 100644 --- a/src/types.jl +++ b/src/types.jl @@ -127,23 +127,6 @@ function compressed_datastream(io, tkey) return read(io, tkey.fNbytes - tkey.fKeylen) end -# Based on -# https://github.com/jakobnissen/LibDeflate.jl/blob/d172c4429404a6ce73b3d44541e80d6f69a0f38a/src/zlib.jl#L38 -function _decompress_zlib!( - output::AbstractArray{UInt8}, - input, - n_out::Integer -) - GC.@preserve output input unsafe_zlib_decompress!( - Base.HasLength(), - Decompressor(), - pointer(output), - n_out, - pointer(input), - sizeof(input) - ) -end - function _decompress_lz4!(input_ptr, input_size, output_ptr, output_size) CodecLz4.LZ4_decompress_safe(input_ptr, output_ptr, input_size, output_size) nothing @@ -180,7 +163,7 @@ function decompress_datastreambytes(compbytes, tkey) _decompress_lz4!(input_ptr, input_size, output_ptr, output_size) elseif cname == "ZL" output = @view(uncomp_data[fufilled+1:fufilled+uncompbytes]) - _decompress_zlib!(output, rawbytes, uncompbytes) + zlib_decompress!(Decompressor(), output, rawbytes, uncompbytes) elseif cname == "XZ" @view(uncomp_data[fufilled+1:fufilled+uncompbytes]) .= transcode(XzDecompressor, rawbytes) elseif cname == "ZS" From 47ba3bf694de9e05d68a336116df77e69250388d Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Wed, 1 Dec 2021 09:33:14 -0500 Subject: [PATCH 34/59] Update ci.yml --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22f7efcc..85b03cc3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,7 @@ jobs: fail-fast: false matrix: version: + - '1.6' - '1.5' - '1.4' - '1.3' # Replace this with the minimum Julia version that your package supports. E.g. if your package requires Julia 1.5 or higher, change this to '1.5'. From 2d028ce0ef0bb1318ede775eabdc4620b7fcdf37 Mon Sep 17 00:00:00 2001 From: Tamas Gal Date: Thu, 20 Jan 2022 19:53:14 +0100 Subject: [PATCH 35/59] Fix gitlab ci (#141) * Use two threads * Remove Julia 1.3 * Oh, we still support Julia 1.3, readding * Simplify CI and fix threading in Julia 1.3 --- .gitlab-ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0d753315..d9542168 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,9 +4,8 @@ stages: .test_template: &test_definiton | export JULIA_PROJECT=@. export JL_PKG=ROOTIO - julia --color=yes -e "if VERSION < v\"0.7.0-DEV.5183\"; Pkg.clone(pwd()); Pkg.build(\"${JL_PKG}\"); else using Pkg; if VERSION >= v\"1.1.0-rc1\"; Pkg.build(verbose=true); else Pkg.build(); end; end" - julia --check-bounds=yes --color=yes -e "if VERSION < v\"0.7.0-DEV.5183\"; Pkg.test(\"${JL_PKG}\", coverage=true); else using Pkg; Pkg.test(coverage=true); end" - + export JULIA_NUM_THREADS=3 + julia --check-bounds=yes --color=yes -e "using Pkg; Pkg.test(coverage=true)" test-julia-1.3: image: julia:1.3 From d41491dad62d81e26ca65d3936cd722a5a1304e9 Mon Sep 17 00:00:00 2001 From: Tamas Gal Date: Tue, 8 Feb 2022 00:48:54 +0100 Subject: [PATCH 36/59] Improve thread handling in tests (#144) * Improve thread handling in tests * Test threading --- test/runtests.jl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 6854bab4..3e9b4272 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -625,6 +625,7 @@ end if get(ENV, "CI", "false") == "true" + # Make sure CI runs with more than 1 thread @test Threads.nthreads() > 1 end nmus = zeros(Int, Threads.nthreads()) @@ -645,7 +646,9 @@ end @static if VERSION > v"1.5.1" t = LazyTree(ROOTFile(joinpath(SAMPLES_DIR, "NanoAODv5_sample.root")), "Events", ["Muon_pt"]) @testset "Multi threading" begin - nmus = zeros(Int, Threads.nthreads()) + nthreads = Threads.nthreads() + nthreads == 1 && @warn "Running on a single thread. Please re-run the test suite with at least two threads (`julia --threads 2 ...`)" + nmus = zeros(Int, nthreads) Threads.@threads for (i, evt) in enumerate(t) nmus[Threads.threadid()] += length(t.Muon_pt[i]) end @@ -655,7 +658,7 @@ end Threads.@threads for evt in t nmus[Threads.threadid()] += length(evt.Muon_pt) end - @test count(>(0), nmus) > 1 # test @threads is actually threading + nthreads > 1 && @test count(>(0), nmus) > 1 # test @threads is actually threading @test sum(nmus) == 878 @@ -663,7 +666,7 @@ end @batch for evt in t nmus[Threads.threadid()] += length(evt.Muon_pt) end - @test count(>(0), nmus) > 1 # test @batch is actually threading + nthreads > 1 && @test count(>(0), nmus) > 1 # test @threads is actually threading @test sum(nmus) == 878 nmus .= 0 @@ -674,7 +677,7 @@ end @test sum(nmus) == 2*878 for j in 1:3 - inds = [Vector{Int}() for _ in 1:Threads.nthreads()] + inds = [Vector{Int}() for _ in 1:nthreads] Threads.@threads for (i, evt) in enumerate(t) push!(inds[Threads.threadid()], i) end From c309eb7c143fdfb5b88138c5270c8e3f186094a0 Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Wed, 23 Feb 2022 18:28:04 -0500 Subject: [PATCH 37/59] faster vcat (#152) --- src/iteration.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/iteration.jl b/src/iteration.jl index e5fb5267..f644a7ef 100644 --- a/src/iteration.jl +++ b/src/iteration.jl @@ -240,8 +240,11 @@ Base.ndims(::Type{<:LazyTree}) = 1 Base.size(lt::LazyTree) = size(innertable(lt)) function LazyArrays.Vcat(ts::LazyTree...) - cs = Tables.columns.(innertable.(ts)) - LazyTree(TypedTables.Table(map(Vcat, cs...))) + branch_names = propertynames(first(ts)) + res_branches = map(branch_names) do bname + LazyArrays.Vcat(getproperty.(ts, bname)...) + end + LazyTree(TypedTables.Table(NamedTuple{branch_names}(res_branches))) end Base.vcat(ts::LazyTree...) = Vcat(ts...) Base.reduce(::typeof(vcat), ts::AbstractVector{<:LazyTree}) = Vcat((ts)...) From 39eb35bc655d8ea375351005681e927dd4a60116 Mon Sep 17 00:00:00 2001 From: Tamas Gal Date: Thu, 24 Feb 2022 10:41:57 +0100 Subject: [PATCH 38/59] Bump version number --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index b2c4e8b6..dbbe0cfa 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnROOT" uuid = "3cd96dde-e98d-4713-81e9-a4a1b0235ce9" authors = ["Tamas Gal", "Jerry Ling", "Johannes Schumann", "Nick Amin"] -version = "0.8.1" +version = "0.8.2" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From cc39f3eb81bf09cad2e796f4f0f905b97effac79 Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Fri, 25 Feb 2022 16:10:53 -0500 Subject: [PATCH 39/59] fix stacktrace too long (#149) * fix stacktrace too long * fix stacktrace too long try 2 * fix stacktrace too long try 3 * fix before 1.6 --- src/UnROOT.jl | 1 + src/displays.jl | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/UnROOT.jl b/src/UnROOT.jl index 17c966b8..aea5830f 100644 --- a/src/UnROOT.jl +++ b/src/UnROOT.jl @@ -17,6 +17,7 @@ import LibDeflate: zlib_decompress!, Decompressor import Tables, TypedTables, PrettyTables @static if VERSION < v"1.6" + Base.first(itr, n::Integer) = collect(Iterators.take(itr, n)) Base.first(a::AbstractVector{S}, n::Integer) where S<: AbstractString = a[1:(length(a) > n ? n : end)] Base.first(a::S, n::Integer) where S<: AbstractString = a[1:(length(a) > n ? n : end)] end diff --git a/src/displays.jl b/src/displays.jl index 6c82e270..6335a85b 100644 --- a/src/displays.jl +++ b/src/displays.jl @@ -77,6 +77,15 @@ function _show(io::IO, tree::LazyTree; kwargs...) ) nothing end + +# stop crazy stracktrace +function Base.show(io::IO, + ::Type{<:LazyTree{<:UnROOT.TypedTables.Table{NamedTuple{Ns, Vs}}}}) where {T, Ns, Vs} + elip = length(Ns) > 5 ? "..." : "" + println(io, "LazyTree with $(length(Ns)) branches:") + println(io, join(first(Ns, 5), ", "), elip) +end + function Base.show(io::IO, ::MIME"text/html", tree::LazyTree) _hs = _make_header(tree) maxrows = 10 From d35aa9ce872d7a66de961cec5103d61ecc9c6f24 Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Wed, 2 Mar 2022 01:18:49 -0500 Subject: [PATCH 40/59] reduce number of read during `ROOTFile` and basket unpacking, use more async, add remote (HTTP) source, (#154) * reduce basket reading io from 4 -> 1 * use more OffsetBuffer when appropriate * bump compatibility * add HTTP source support * Mmap based local file * async chunking * better error * 100% lock-free * scitoken ready * faster display branch * backport upgrade --- .github/workflows/ci.yml | 6 +- Project.toml | 3 + README.md | 12 ++++ docs/make.jl | 1 + docs/src/exampleusage.md | 19 +++++++ src/UnROOT.jl | 26 ++++++++- src/bootstrap.jl | 2 + src/displays.jl | 18 +++++- src/iteration.jl | 16 +++--- src/root.jl | 101 ++++++++++++++++----------------- src/streamsource.jl | 119 +++++++++++++++++++++++++++++++++++++++ src/types.jl | 2 +- test/runtests.jl | 25 +++++--- 13 files changed, 273 insertions(+), 77 deletions(-) create mode 100644 docs/src/exampleusage.md create mode 100644 src/streamsource.jl diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85b03cc3..5dcc9416 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,10 +15,10 @@ jobs: fail-fast: false matrix: version: - - '1.6' - - '1.5' + - '1.3' - '1.4' - - '1.3' # Replace this with the minimum Julia version that your package supports. E.g. if your package requires Julia 1.5 or higher, change this to '1.5'. + - '1.5' + - '1.6' - '1' # Leave this line unchanged. '1' will automatically expand to the latest stable 1.x release of Julia. - 'nightly' os: diff --git a/Project.toml b/Project.toml index dbbe0cfa..7dce77d5 100644 --- a/Project.toml +++ b/Project.toml @@ -9,6 +9,7 @@ ArraysOfArrays = "65a8f2f4-9b39-5baf-92e2-a9cc46fdf018" CodecLz4 = "5ba52731-8f18-5e0d-9241-30f10d1ec561" CodecXz = "ba30903b-d9e8-5048-a5ec-d1f5b0d4b47b" CodecZstd = "6b39b394-51ab-5f42-8807-6242bab2b4c2" +HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e" LRUCache = "8ac3fa9e-de4c-5943-b1dc-09c6b5f20637" LazyArrays = "5078a376-72f3-5289-bfd5-ec5146d43c02" @@ -16,6 +17,7 @@ LibDeflate = "9255714d-24a7-4b30-8ea3-d46a97f7e13b" LorentzVectors = "3f54b04b-17fc-5cd4-9758-90c048d965e3" Memoization = "6fafb56a-5788-4b4e-91ca-c0cea6611c73" Mixers = "2a8e4939-dab8-5edc-8f64-72a8776f13de" +Mmap = "a63ad114-7e13-5084-954f-fe012c677804" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" @@ -28,6 +30,7 @@ ArraysOfArrays = "^0.5.3" CodecLz4 = "^0.3.0, ^0.4.0" CodecXz = "^0.6.0, ^0.7.0" CodecZstd = "^0.6.0, ^0.7.0" +HTTP = "^0.9.7" IterTools = "^1" LRUCache = "^1.3.0" LazyArrays = "^0.15, ^0.21, ^0.22, ^1" diff --git a/README.md b/README.md index b81f02c3..2fcdba16 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,18 @@ Only one basket per branch will be cached so you don't have to worry about runni At the same time, `event` inside the for-loop is not materialized until a field is accessed. If your event is fairly small or you need all of them anyway, you can `collect(event)` first inside the loop. +XRootD is also supported, depending on the protocol: +- the "url" has to start with `http://` or `https://`: +- (1.6+ only) or the "url" has to start with `root://` and have another `//` to separate server and file path +```julia +julia> r = @time ROOTFile("https://scikit-hep.org/uproot3/examples/Zmumu.root") + 0.034877 seconds (5.13 k allocations: 533.125 KiB) +ROOTFile with 1 entry and 18 streamers. + +julia> r = ROOTFile("root://eospublic.cern.ch//eos/root-eos/cms_opendata_2012_nanoaod/Run2012B_DoubleMuParked.root") +ROOTFile with 1 entry and 19 streamers. +``` + ## Branch of custom struct We provide an experimental interface for hooking up UnROOT with your custom types diff --git a/docs/make.jl b/docs/make.jl index 97c7fbaa..03fc2f89 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -8,6 +8,7 @@ makedocs(; ), pages=[ "Introduction" => "index.md", + "Example Usage" => "exampleusage.md", "Performance Tips" => "performancetips.md", "Advanced Usage" => [ "Parse Custom Branch" => "advanced/custom_branch.md", diff --git a/docs/src/exampleusage.md b/docs/src/exampleusage.md new file mode 100644 index 00000000..05a87b3f --- /dev/null +++ b/docs/src/exampleusage.md @@ -0,0 +1,19 @@ +## Chunk Iteration +```julia +t = LazyTree(...) +res = 0.0 +for rang in Iterators.partition(1:lastindex(t), 10^6) + res += sum(t[rang].nMuon) # +end +res +``` +Note, `t[rang]` is eager, if you don't need all branches, it's much better to use `t.nMuon[rang]`, or limit which +branches are selected during `LazyTree()` creation time. + +This pattern works the best over network, for local files, stick with: +``` +for evt in t + ... +end +``` +usually is the best approach. diff --git a/src/UnROOT.jl b/src/UnROOT.jl index aea5830f..1097951f 100644 --- a/src/UnROOT.jl +++ b/src/UnROOT.jl @@ -1,9 +1,10 @@ module UnROOT using LazyArrays +import Mmap: mmap export ROOTFile, LazyBranch, LazyTree -import Base: close, keys, get, getindex, getproperty, show, length, iterate, position, ntoh, lock, unlock, reinterpret +import Base: close, keys, get, getindex, getproperty, show, length, iterate, position, ntoh, reinterpret ntoh(b::Bool) = b import AbstractTrees: children, printnode, print_tree @@ -22,7 +23,23 @@ import Tables, TypedTables, PrettyTables Base.first(a::S, n::Integer) where S<: AbstractString = a[1:(length(a) > n ? n : end)] end +""" + OffsetBuffer + +Works with seek, position of the original file. Think of it as a view of IOStream that can be +indexed with original positions. +""" +struct OffsetBuffer{T} + io::T + offset::Int +end +Base.read(io::OffsetBuffer, nb) = Base.read(io.io, nb) +Base.seek(io::OffsetBuffer, i) = Base.seek(io.io, i - io.offset) +Base.skip(io::OffsetBuffer, i) = Base.skip(io.io, i) +Base.position(io::OffsetBuffer) = position(io.io) + io.offset + include("constants.jl") +include("streamsource.jl") include("io.jl") include("types.jl") include("utils.jl") @@ -33,4 +50,11 @@ include("iteration.jl") include("custom.jl") include("displays.jl") +if ccall(:jl_generating_output, Cint, ()) == 1 # if we're precompiling the package + let + r = ROOTFile("https://scikit-hep.org/uproot3/examples/Zmumu.root") + show(devnull, r) + end +end + end # module diff --git a/src/bootstrap.jl b/src/bootstrap.jl index 92ef2ea8..ea1b3feb 100644 --- a/src/bootstrap.jl +++ b/src/bootstrap.jl @@ -395,6 +395,8 @@ primitivetype(l::TLeafD) = Float64 fMaximum end +primitivetype(l::TLeafC) = UInt8 + function parsefields!(io, fields, ::Type{T}) where {T<:TLeafC} preamble = Preamble(io, T) parsefields!(io, fields, TLeaf) diff --git a/src/displays.jl b/src/displays.jl index 6335a85b..ce1335fa 100644 --- a/src/displays.jl +++ b/src/displays.jl @@ -14,7 +14,6 @@ function children(f::T) where T <: Union{ROOTFile,ROOTDirectory} # then all TKeys in the file which are not for a TTree seen = Set{String}() ch = Vector{Union{TTree,TKeyNode,ROOTDirectory}}() - T === ROOTFile ? lock(f) : nothing for k in keys(f) try obj = f[k] @@ -35,7 +34,6 @@ function children(f::T) where T <: Union{ROOTFile,ROOTDirectory} push!(ch, kn) end end - T === ROOTFile ? unlock(f) : nothing ch end function children(t::TTree) @@ -78,6 +76,22 @@ function _show(io::IO, tree::LazyTree; kwargs...) nothing end + +function Base.show(io::IO, ::MIME"text/plain", br::LazyBranch) + print(io, summary(br)) + println(": ") + if length(br) < 200 + Base.print_array(IOContext(io, :limit => true), br[:]) + else + head = @async br[1:100] + tail = @async br[end-99:end] + wait(head) + wait(tail) + Base.print_array(IOContext(io, :limit => true), Vcat(head.result, tail.result)) + end + nothing +end + # stop crazy stracktrace function Base.show(io::IO, ::Type{<:LazyTree{<:UnROOT.TypedTables.Table{NamedTuple{Ns, Vs}}}}) where {T, Ns, Vs} diff --git a/src/iteration.jl b/src/iteration.jl index f644a7ef..815930ed 100644 --- a/src/iteration.jl +++ b/src/iteration.jl @@ -122,6 +122,8 @@ mutable struct LazyBranch{T,J,B} <: AbstractVector{T} [0:-1 for _ in 1:Threads.nthreads()]) end end +basketarray(lb::LazyBranch, ithbasket) = basketarray(lb.f, lb.b, ithbasket) +basketarray_iter(lb::LazyBranch) = basketarray_iter(lb.f, lb.b) function Base.hash(lb::LazyBranch, h::UInt) h = hash(lb.f, h) @@ -138,8 +140,6 @@ Base.firstindex(ba::LazyBranch) = 1 Base.lastindex(ba::LazyBranch) = ba.L Base.eltype(ba::LazyBranch{T,J,B}) where {T,J,B} = T -basketarray(lb::LazyBranch, ithbasket) = basketarray(lb.f, lb.b, ithbasket) -basketarray_iter(lb::LazyBranch) = basketarray_iter(lb.f, lb.b) function Base.show(io::IO, lb::LazyBranch) summary(io, lb) @@ -213,14 +213,17 @@ Base.IndexStyle(::Type{<:LazyTree}) = IndexLinear() Base.getindex(lt::LazyTree, row::Int) = LazyEvent(innertable(lt), row) # kept lazy for broadcasting purpose Base.getindex(lt::LazyTree, row::CartesianIndex{1}) = LazyEvent(innertable(lt), row[1]) -function Base.getindex(lt::LazyTree, rang::UnitRange) - return LazyTree(innertable(lt)[rang]) +function Base.getindex(lt::LazyTree, rang) + bnames = propertynames(lt) + branches = asyncmap(b->getproperty(lt, b)[rang], bnames) + return LazyTree(TypedTables.Table(NamedTuple{bnames}(branches))) end # a specific event Base.getindex(lt::LazyTree, ::typeof(!), s::Symbol) = lt[:, s] Base.getindex(lt::LazyTree, ::Colon, s::Symbol) = getproperty(innertable(lt), s) # the real deal Base.getindex(lt::LazyTree, row::Int, col::Symbol) = lt[:, col][row] +Base.getindex(lt::LazyTree, row, ::Colon) = lt[row] Base.getindex(lt::LazyTree, rows::UnitRange, col::Symbol) = lt[:, col][rows] Base.getindex(lt::LazyTree, ::Colon) = lt[1:end] Base.firstindex(lt::LazyTree) = 1 @@ -290,9 +293,6 @@ julia> mytree = LazyTree(f, "Events", ["Electron_dxy", "nMuon", r"Muon_(pt|eta)\ function LazyTree(f::ROOTFile, s::AbstractString, branches) tree = f[s] tree isa TTree || error("$s is not a tree name.") - if length(branches) > 30 - @warn "Your tree is quite wide, with $(length(branches)) branches, this will take compiler a moment." - end d = Dict{Symbol,LazyBranch}() _m(s::AbstractString) = isequal(s) _m(r::Regex) = Base.Fix1(occursin, r) @@ -336,7 +336,7 @@ function Base.getindex(ba::LazyBranch{T,J,B}, range::UnitRange) where {T,J,B} ib2 = findfirst(x -> x > (last(range) - 1), ba.fEntry) - 1 offset = ba.fEntry[ib1] range = (first(range)-offset):(last(range)-offset) - return vcat([basketarray(ba, i) for i in ib1:ib2]...)[range] + return Vcat(asyncmap(i->basketarray(ba, i), ib1:ib2)...)[range] end _clusterranges(t::LazyTree) = _clusterranges([getproperty(t,p) for p in propertynames(t)]) diff --git a/src/root.jl b/src/root.jl index 0969072a..1c9ba5ce 100644 --- a/src/root.jl +++ b/src/root.jl @@ -2,23 +2,21 @@ struct ROOTDirectory name::AbstractString header::ROOTDirectoryHeader keys::Vector{TKey} - fobj::IOStream + fobj::SourceStream refs::Dict{Int32, Any} end struct ROOTFile - filename::AbstractString + filename::String format_version::Int32 header::FileHeader - fobj::IOStream + fobj::SourceStream tkey::TKey streamers::Streamers directory::ROOTDirectory customstructs::Dict{String, Type} - lk::ReentrantLock end function close(f::ROOTFile) - # TODO: should we take care of the lock? close(f.fobj) end function ROOTFile(f::Function, args...; pv...) @@ -29,8 +27,7 @@ function ROOTFile(f::Function, args...; pv...) close(rootfile) end end -lock(f::ROOTFile) = lock(f.lk) -unlock(f::ROOTFile) = unlock(f.lk) + function Base.hash(rf::ROOTFile, h::UInt) hash(rf.fobj, h) end @@ -58,45 +55,51 @@ test/samples/NanoAODv5_sample.root └─ "⋮" ``` """ +const HEAD_BUFFER_SIZE = 1024 function ROOTFile(filename::AbstractString; customstructs = Dict("TLorentzVector" => LorentzVector{Float64})) - fobj = Base.open(filename) - preamble = unpack(fobj, FilePreamble) - String(preamble.identifier) == "root" || error("Not a ROOT file!") + fobj = if startswith(filename, r"https?://") + HTTPStream(filename) + else + !isfile(filename) && "$filename is not a file" + MmapStream(filename) + end + header_bytes = read(fobj, HEAD_BUFFER_SIZE) + @assert header_bytes[1:4] == [0x72, 0x6f, 0x6f, 0x74] "$filename is not a ROOT file." + head_buffer = IOBuffer(header_bytes) + preamble = unpack(head_buffer, FilePreamble) format_version = preamble.fVersion - if format_version < 1000000 + header = if format_version < 1000000 @debug "32bit ROOT file" - header = unpack(fobj, FileHeader32) + unpack(head_buffer, FileHeader32) else @debug "64bit ROOT file" - header = unpack(fobj, FileHeader64) + unpack(head_buffer, FileHeader64) end # Streamers - if header.fSeekInfo != 0 - @debug "Reading streamer info." - seek(fobj, header.fSeekInfo) - streamers = Streamers(fobj) - else - @debug "No streamer info present, skipping." - end + seek(fobj, header.fSeekInfo) + stream_buffer = OffsetBuffer(IOBuffer(read(fobj, 10^5)), Int(header.fSeekInfo)) + streamers = Streamers(stream_buffer) - seek(fobj, header.fBEGIN) - tkey = unpack(fobj, TKey) + seek(head_buffer, header.fBEGIN + header.fNbytesName) + dir_header = unpack(head_buffer, ROOTDirectoryHeader) + dirkey = dir_header.fSeekKeys + seek(fobj, dirkey) + tail_buffer = @async IOBuffer(read(fobj, 10^6)) - # Reading the header key for the top ROOT directory - seek(fobj, header.fBEGIN + header.fNbytesName) - dir_header = unpack(fobj, ROOTDirectoryHeader) + seek(head_buffer, header.fBEGIN) + tkey = unpack(head_buffer, TKey) - seek(fobj, dir_header.fSeekKeys) - header_key = unpack(fobj, TKey) + wait(tail_buffer) + unpack(tail_buffer.result, TKey) - n_keys = readtype(fobj, Int32) - keys = [unpack(fobj, TKey) for _ in 1:n_keys] + n_keys = readtype(tail_buffer.result, Int32) + keys = [unpack(tail_buffer.result, TKey) for _ in 1:n_keys] directory = ROOTDirectory(tkey.fName, dir_header, keys, fobj, streamers.refs) - ROOTFile(filename, format_version, header, fobj, tkey, streamers, directory, customstructs, ReentrantLock()) + ROOTFile(filename, format_version, header, fobj, tkey, streamers, directory, customstructs) end function Base.show(io::IO, f::ROOTFile) @@ -146,14 +149,8 @@ end tkey = f.directory.keys[findfirst(isequal(s), keys(f))] @debug "Retrieving $s ('$(tkey.fClassName)')" streamer = getfield(@__MODULE__, Symbol(tkey.fClassName)) - lock(f) - try - S = streamer(f.fobj, tkey, f.streamers.refs) - return S - catch - finally - unlock(f) - end + S = streamer(f.fobj, tkey, f.streamers.refs) + return S end # FIXME unify with above? @@ -409,9 +406,9 @@ function readbranchraw(f::ROOTFile, branch) datas = sizehint!(Vector{UInt8}(), sum(nbytes)) # maximum length if all data are UInt8 offsets = sizehint!(zeros(Int32, 1), branch.fEntries+1) # this is always Int32 position = 0 - foreach(branch.fBasketSeek) do seek - seek==0 && return - data, offset = readbasketseek(f, branch, seek) + for (seek, nb) in zip(branch.fBasketSeek, nbytes) + seek==0 && break + data, offset = readbasketseek(f, branch, seek, nb) append!(datas, data) # FIXME: assuming offset has always 0 or at least 2 elements ;) append!(offsets, (@view offset[2:end]) .+ position) @@ -435,7 +432,7 @@ end # 3GB cache for baskets """ readbasket(f::ROOTFile, branch, ith) - readbasketseek(f::ROOTFile, branch::Union{TBranch, TBranchElement}, seek_pos::Int) + readbasketseek(f::ROOTFile, branch::Union{TBranch, TBranchElement}, seek_pos::Int, nbytes) The fundamental building block of reading read data from a .root file. Read read one basket's raw bytes and offsets at a time. These raw bytes and offsets then (potentially) get @@ -443,19 +440,15 @@ processed by [`interped_data`](@ref). See also: [`auto_T_JaggT`](@ref), [`basketarray`](@ref) """ -readbasket(f::ROOTFile, branch, ith) = readbasketseek(f, branch, branch.fBasketSeek[ith]) +function readbasket(f::ROOTFile, branch, ith) + readbasketseek(f, branch, branch.fBasketSeek[ith], branch.fBasketBytes[ith]) +end -function readbasketseek(f::ROOTFile, branch::Union{TBranch, TBranchElement}, seek_pos::Int) - lock(f) - local basketkey, compressedbytes - try - seek(f.fobj, seek_pos) - basketkey = unpack(f.fobj, TBasketKey) - compressedbytes = compressed_datastream(f.fobj, basketkey) - catch - finally - unlock(f) - end +function readbasketseek(f::ROOTFile, branch::Union{TBranch, TBranchElement}, seek_pos::Int, nb) + local rawbuffer + rawbuffer = OffsetBuffer(IOBuffer(read_seek_nb(f.fobj, seek_pos, nb)), seek_pos) + basketkey = unpack(rawbuffer, TBasketKey) + compressedbytes = compressed_datastream(rawbuffer, basketkey) basketrawbytes = decompress_datastreambytes(compressedbytes, basketkey) diff --git a/src/streamsource.jl b/src/streamsource.jl new file mode 100644 index 00000000..9ff4b005 --- /dev/null +++ b/src/streamsource.jl @@ -0,0 +1,119 @@ +import HTTP + +mutable struct MmapStream # Mmap based + mmap_ary::Vector{UInt8} + seekloc::Int + size::Int + function MmapStream(filepath::AbstractString) + size = filesize(filepath) + new(mmap(filepath), 0, size) + end +end + +read_seek_nb(fobj::MmapStream, seek, nb) = fobj.mmap_ary[seek+1:seek+nb] + +function Base.read(fobj::MmapStream, nb::Integer) + stop = min(fobj.seekloc + nb, fobj.size) + b = fobj.mmap_ary[fobj.seekloc+1 : stop] + fobj.seekloc += nb + return b +end + +function Base.close(fobj::MmapStream) # no-op + nothing +end + +# SciToken discovery https://zenodo.org/record/3937438 +function _find_scitoken() + op1 = get(ENV, "BEARER_TOKEN", "") + op2 = get(ENV, "BEARER_TOKEN_FILE", "") + op3 = get(ENV, "XDG_RUNTIME_DIR", "") + uid = strip(read(`id -u`, String)) + op3_file = joinpath(op3, "bt_u$uid") + op4_file = "/tmp/bt_u$uid" + token = if !isempty(op1) + op1 + elseif !isempty(op2) + read(op2, String) + elseif !isempty(op3) && isfile(op3_file) + read(op3_file, String) + elseif isfile(op4_file) + read(op4_file, String) + else + "" + end + return strip(token) +end + +mutable struct HTTPStream + uri::HTTP.URI + seekloc::Int + size::Int + multipart::Bool + scitoken::String + function HTTPStream(uri::AbstractString; scitoken = _find_scitoken()) + #TODO: determin multipart support + test = HTTP.request("GET", uri, + ("Range" => "bytes=0-3", "User-Agent" => "UnROOTjl", "Authorization" => "Bearer $scitoken") + ) + @assert test.status==206 "bad network or wrong server" + @assert String(test.body)=="root" "not a root file" + multipart = false + local v + for pair in test.headers + if lowercase(pair[1]) == "content-range" + v = pair[2] + break + end + end + size = parse(Int, match(r"/(\d+)", v).captures[1]) + new(HTTP.URI(uri), 0, size, multipart, scitoken) + end +end + +const SourceStream = Union{MmapStream, HTTPStream} + +function Base.read(fobj::SourceStream, ::Type{T}) where T + return reinterpret(T, read(fobj, sizeof(T)))[1] # TODO: use `only` +end + +function Base.position(fobj::SourceStream) + fobj.seekloc +end + +function Base.seek(fobj::SourceStream, loc) + fobj.seekloc = loc + return fobj +end + +function Base.skip(fobj::SourceStream, stride) + fobj.seekloc += stride + return fobj +end + +function Base.seekstart(fobj::SourceStream) + fobj.seekloc = 0 + return fobj +end + +function Base.close(fobj::HTTPStream) # no-op + nothing +end + +function Base.read(fobj::HTTPStream, nb::Integer) + @debug nb + b = read_seek_nb(fobj, fobj.seekloc, nb) + fobj.seekloc += nb + return b +end + +function read_seek_nb(fobj::HTTPStream, seek, nb) + stop = seek+nb-1 + hd = ("Range" => "bytes=$(seek)-$stop", "Authorization" => "Bearer $(fobj.scitoken)") + b = HTTP.request(HTTP.stack(), "GET", fobj.uri, hd, UInt8[]).body + return b +end + +function Base.read(fobj::SourceStream) + read(fobj, fobj.size - fobj.seekloc + 1) +end diff --git a/src/types.jl b/src/types.jl index ce0ac1ff..bf7af636 100644 --- a/src/types.jl +++ b/src/types.jl @@ -238,7 +238,7 @@ end const ROOTDirectoryHeader = Union{ROOTDirectoryHeader32, ROOTDirectoryHeader64} -function unpack(io::IOStream, ::Type{ROOTDirectoryHeader}) +function unpack(io, ::Type{ROOTDirectoryHeader}) fVersion = readtype(io, Int16) skip(io, -2) diff --git a/test/runtests.jl b/test/runtests.jl index 3e9b4272..490ddabf 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -583,16 +583,18 @@ end end @testset "Type stability" begin - function isfullystable(func) - io = IOBuffer() - print(io, (@code_typed func()).first); - typed = String(take!(io)) - return !occursin("::Any", typed) - end + # function isfullystable(func) + # io = IOBuffer() + # print(io, (@code_typed func()).first); + # typed = String(take!(io)) + # println(typed) + # return !occursin("::Any", typed) + # end rootfile = ROOTFile(joinpath(SAMPLES_DIR, "NanoAODv5_sample.root")) t = LazyTree(rootfile, "Events", ["MET_pt"])[1:10] + # LazyArray has a BoundsArray that is ::Any but is "fine" function f1() s = 0.0f0 for evt in t @@ -602,8 +604,8 @@ end end f2() = sum(t.MET_pt) - @test isfullystable(f1) - @test isfullystable(f2) + @inferred f1() + @inferred f2() close(rootfile) end @@ -712,6 +714,13 @@ end @test length(UnROOT.basketarray(t.b1, 1)) == 1228 end +@testset "SourceStream remote" begin + t = LazyTree("https://scikit-hep.org/uproot3/examples/Zmumu.root", "events") + @test t.eta1[1] ≈ -1.21769 + @test t.eta1[end] ≈ -1.57044 + show(devnull, t) # test display +end + @testset "Cluster ranges" begin t = LazyTree(UnROOT.samplefile("tree_with_clusters.root"),"t1"); @test all(UnROOT._clusterbytes(t; compressed=true) .< 10000) From e60d17fa3477ded4da7b354dc5bc28ffd330832d Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Wed, 2 Mar 2022 01:19:12 -0500 Subject: [PATCH 41/59] bump version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 7dce77d5..e5dce610 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnROOT" uuid = "3cd96dde-e98d-4713-81e9-a4a1b0235ce9" authors = ["Tamas Gal", "Jerry Ling", "Johannes Schumann", "Nick Amin"] -version = "0.8.2" +version = "0.8.3" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From f6a0d4afdc6164cb8716b58bb91b9d9eb3341f25 Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Fri, 11 Mar 2022 10:53:26 -0500 Subject: [PATCH 42/59] improve LV and add a debug for compression type --- src/custom.jl | 3 ++- src/types.jl | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/custom.jl b/src/custom.jl index 7d1c6490..e69da402 100644 --- a/src/custom.jl +++ b/src/custom.jl @@ -38,7 +38,8 @@ Base.show(io::IO, lv::LorentzVector) = print(io, "LV(x=$(lv.x), y=$(lv.y), z=$(l function Base.reinterpret(::Type{LVF64}, v::AbstractVector{UInt8}) where T # first 32 bytes are TObject header we don't care # x,y,z,t in ROOT - v4 = ntoh.(reinterpret(Float64, @view v[1+32:end])) + v4 = (reinterpret(Float64, @view v[1+32:end])) + v4 .= ntoh.(v4) # t,x,y,z in LorentzVectors.jl LVF64(v4[4], v4[1], v4[2], v4[3]) end diff --git a/src/types.jl b/src/types.jl index bf7af636..124a026d 100644 --- a/src/types.jl +++ b/src/types.jl @@ -151,6 +151,7 @@ function decompress_datastreambytes(compbytes, tkey) compression_header = unpack(io, CompressionHeader) cname, _, compbytes, uncompbytes = unpack(compression_header) rawbytes = read(io, compbytes) + @debug cname if cname == "L4" # skip checksum which is 8 bytes From 00e7815c3892027d1ee221a0e3ea1627ea405e09 Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Tue, 15 Mar 2022 14:54:05 -0400 Subject: [PATCH 43/59] fix split branches (#155) * fix split branches --- src/iteration.jl | 22 ++++++++++++++++------ src/root.jl | 2 ++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/iteration.jl b/src/iteration.jl index 815930ed..e6437aeb 100644 --- a/src/iteration.jl +++ b/src/iteration.jl @@ -258,7 +258,9 @@ function getbranchnamesrecursive(obj) out = Vector{String}() for b in obj.fBranches.elements push!(out, b.fName) - for subname in getbranchnamesrecursive(b) + subs = getbranchnamesrecursive(b) + !isempty(subs) && pop!(out) + for subname in subs push!(out, "$(b.fName)/$(subname)") end end @@ -294,12 +296,20 @@ function LazyTree(f::ROOTFile, s::AbstractString, branches) tree = f[s] tree isa TTree || error("$s is not a tree name.") d = Dict{Symbol,LazyBranch}() - _m(s::AbstractString) = isequal(s) _m(r::Regex) = Base.Fix1(occursin, r) - branches = mapreduce(b -> filter(_m(b), getbranchnamesrecursive(tree)), ∪, branches) - SB = Symbol.(branches) - for b in SB - d[b] = f["$s/$b"] + all_bnames = getbranchnamesrecursive(tree) + res_bnames = mapreduce(∪, branches) do b + if b isa Regex + filter(_m(b), all_bnames) + elseif b isa String + expand = filter(n->startswith(n, "$b/$b"), all_bnames) + isempty(expand) ? filter(isequal(b), all_bnames) : expand + else + error("branch selection must be string or regex") + end + end + for b in res_bnames + d[Symbol(b)] = f["$s/$b"] end return LazyTree(TypedTables.Table(d)) end diff --git a/src/root.jl b/src/root.jl index 1c9ba5ce..bbeff6e4 100644 --- a/src/root.jl +++ b/src/root.jl @@ -362,6 +362,8 @@ function auto_T_JaggT(f::ROOTFile, branch; customstructs::Dict{String, Type}) Bool elseif elname == "unsigned int" UInt32 + elseif elname == "signed char" + Int8 elseif elname == "unsigned char" UInt8 elseif elname == "unsigned short" From f6339ccd89866a8233c62fc58609be7da5b7cf5d Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Tue, 15 Mar 2022 14:54:28 -0400 Subject: [PATCH 44/59] bump version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index e5dce610..7b1eb3e1 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnROOT" uuid = "3cd96dde-e98d-4713-81e9-a4a1b0235ce9" authors = ["Tamas Gal", "Jerry Ling", "Johannes Schumann", "Nick Amin"] -version = "0.8.3" +version = "0.8.4" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From e822e5d65a4220105221f97210090c5af2ce7df6 Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Tue, 5 Apr 2022 13:34:15 -0400 Subject: [PATCH 45/59] fix windows `id -u` (#158) * fix windows `id -u` * remove hacky precompile * fix CI --- src/UnROOT.jl | 7 ------- src/streamsource.jl | 6 +++++- test/runtests.jl | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/UnROOT.jl b/src/UnROOT.jl index 1097951f..d4e74334 100644 --- a/src/UnROOT.jl +++ b/src/UnROOT.jl @@ -50,11 +50,4 @@ include("iteration.jl") include("custom.jl") include("displays.jl") -if ccall(:jl_generating_output, Cint, ()) == 1 # if we're precompiling the package - let - r = ROOTFile("https://scikit-hep.org/uproot3/examples/Zmumu.root") - show(devnull, r) - end -end - end # module diff --git a/src/streamsource.jl b/src/streamsource.jl index 9ff4b005..78935cf6 100644 --- a/src/streamsource.jl +++ b/src/streamsource.jl @@ -28,7 +28,11 @@ function _find_scitoken() op1 = get(ENV, "BEARER_TOKEN", "") op2 = get(ENV, "BEARER_TOKEN_FILE", "") op3 = get(ENV, "XDG_RUNTIME_DIR", "") - uid = strip(read(`id -u`, String)) + uid = @static if Sys.iswindows() + "julia" + else + strip(read(`id -u`, String)) + end op3_file = joinpath(op3, "bt_u$uid") op4_file = "/tmp/bt_u$uid" token = if !isempty(op1) diff --git a/test/runtests.jl b/test/runtests.jl index 490ddabf..d10663eb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -665,7 +665,7 @@ end nmus .= 0 - @batch for evt in t + Threads.@threads for evt in t nmus[Threads.threadid()] += length(evt.Muon_pt) end nthreads > 1 && @test count(>(0), nmus) > 1 # test @threads is actually threading From 03614f4fa8f8ea77a1934af67901cb0f5f1e1206 Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Tue, 5 Apr 2022 13:34:34 -0400 Subject: [PATCH 46/59] Update Project.toml bump version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 7b1eb3e1..5e156837 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnROOT" uuid = "3cd96dde-e98d-4713-81e9-a4a1b0235ce9" authors = ["Tamas Gal", "Jerry Ling", "Johannes Schumann", "Nick Amin"] -version = "0.8.4" +version = "0.8.5" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 4df552ea130b01af8850407dc172790849c48ba5 Mon Sep 17 00:00:00 2001 From: Tamas Gal Date: Tue, 5 Apr 2022 21:01:39 +0200 Subject: [PATCH 47/59] Fix URLs (#159) --- README.md | 18 +++++++++--------- docs/make.jl | 2 +- docs/src/advanced/custom_branch.md | 10 +++++----- test/samples/issue61.py | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 2fcdba16..4b32be80 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,14 @@ [![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-) -[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://tamasgal.github.io/UnROOT.jl/dev) -[![Build Status](https://github.com/tamasgal/UnROOT.jl/workflows/CI/badge.svg)](https://github.com/tamasgal/UnROOT.jl/actions) -[![Codecov](https://codecov.io/gh/tamasgal/UnROOT.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/tamasgal/UnROOT.jl) +[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://juliahep.github.io/UnROOT.jl/dev) +[![Build Status](https://github.com/JuliaHEP/UnROOT.jl/workflows/CI/badge.svg)](https://github.com/JuliaHEP/UnROOT.jl/actions) +[![Codecov](https://codecov.io/gh/JuliaHEP/UnROOT.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/JuliaHEP/UnROOT.jl) UnROOT.jl is a reader for the [CERN ROOT](https://root.cern) file format written entirely in Julia, without any dependence on ROOT or Python. -## Quick Start (see [docs](https://tamasgal.github.io/UnROOT.jl/dev/) for more) +## Quick Start (see [docs](https://JuliaHEP.github.io/UnROOT.jl/dev/) for more) ```julia julia> using UnROOT @@ -69,7 +69,7 @@ ROOTFile with 1 entry and 19 streamers. ## Branch of custom struct We provide an experimental interface for hooking up UnROOT with your custom types -that only takes 2 steps, as explained [in the docs](https://tamasgal.github.io/UnROOT.jl/dev/advanced/custom_branch/). +that only takes 2 steps, as explained [in the docs](https://JuliaHEP.github.io/UnROOT.jl/dev/advanced/custom_branch/). As a show case for this functionality, the `TLorentzVector` support in UnROOT is implemented with the said plug-in system. @@ -202,10 +202,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d - - - - + + + + diff --git a/docs/make.jl b/docs/make.jl index 03fc2f89..bb4837a7 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -17,7 +17,7 @@ makedocs(; "For Contributors" => "devdocs.md", "APIs" => "internalapis.md", ], - repo="https://github.com/tamasgal/UnROOT.jl/blob/{commit}{path}#L{line}", + repo="https://github.com/JuliaHEP/UnROOT.jl/blob/{commit}{path}#L{line}", sitename="UnROOT.jl", authors="Tamas Gal and contributors", ) diff --git a/docs/src/advanced/custom_branch.md b/docs/src/advanced/custom_branch.md index 5f71367c..bcc1f003 100644 --- a/docs/src/advanced/custom_branch.md +++ b/docs/src/advanced/custom_branch.md @@ -3,17 +3,17 @@ It is possible to parse Branches with custom structure as long as you know how t As an example, the `TLorentzVector` is added using this mechanism and we will walk through the steps needed: ### 1. Provide a map between `fClassName` of your struct (as seen in .root) to a Julia type. -Pass a `Dict{String, Type}` to `ROOTFile(filepath; customstructs)`. The `TLorentzVector` is shipped [by default](https://github.com/tamasgal/UnROOT.jl/blob/06b692523bbff3f467f6b7fe3544e411a719bc9e/src/root.jl#L21): +Pass a `Dict{String, Type}` to `ROOTFile(filepath; customstructs)`. The `TLorentzVector` is shipped [by default](https://github.com/JuliaHEP/UnROOT.jl/blob/06b692523bbff3f467f6b7fe3544e411a719bc9e/src/root.jl#L21): ```julia ROOTFile(filepath; customstructs = Dict("TLorentzVector" => LorentzVector{Float64})) ``` -This `Dict` will subsequently be used by the `auto_T_JaggT` function [at here](https://github.com/tamasgal/UnROOT.jl/blob/06b692523bbff3f467f6b7fe3544e411a719bc9e/src/root.jl#L213-L222) such that when we encounter a branch with this `fClassName`, we will return your `Type` as the detected element type of this branch. +This `Dict` will subsequently be used by the `auto_T_JaggT` function [at here](https://github.com/JuliaHEP/UnROOT.jl/blob/06b692523bbff3f467f6b7fe3544e411a719bc9e/src/root.jl#L213-L222) such that when we encounter a branch with this `fClassName`, we will return your `Type` as the detected element type of this branch. ### 2. Extend the raw bytes interpreting function `UnROOT.interped_data` -By default, given a branch element type and a "jaggness" type, a general function [is defined](https://github.com/tamasgal/UnROOT.jl/blob/06b692523bbff3f467f6b7fe3544e411a719bc9e/src/root.jl#L149) which will try to parse the raw bytes into Julia data structure. The `::Type{T}` will match what you have provided in the `Dict` in the previous step. +By default, given a branch element type and a "jaggness" type, a general function [is defined](https://github.com/JuliaHEP/UnROOT.jl/blob/06b692523bbff3f467f6b7fe3544e411a719bc9e/src/root.jl#L149) which will try to parse the raw bytes into Julia data structure. The `::Type{T}` will match what you have provided in the `Dict` in the previous step. -Thus, to "teach" UnROOT how to interpret bytes for your type `T`, you would want to defined a more specific `UnROOT.interped_data` than the default one. Taking the `TLorentzVector` [as example](https://github.com/tamasgal/UnROOT.jl/blob/06b692523bbff3f467f6b7fe3544e411a719bc9e/src/custom.jl#L23) again, we define a function: +Thus, to "teach" UnROOT how to interpret bytes for your type `T`, you would want to defined a more specific `UnROOT.interped_data` than the default one. Taking the `TLorentzVector` [as example](https://github.com/JuliaHEP/UnROOT.jl/blob/06b692523bbff3f467f6b7fe3544e411a719bc9e/src/custom.jl#L23) again, we define a function: ```julia using LorentzVector const LVF64 = LorentzVector{Float64} @@ -32,7 +32,7 @@ function Base.reinterpret(::Type{LVF64}, v::AbstractVector{UInt8}) where T end ``` -The `Base.reinterpret` function is just a helper function, you could instead write everything inside `UnROOT.interped_data`. We then builds on these, to interpret Jagged TLV branch: https://github.com/tamasgal/UnROOT.jl/blob/4747f6f5fd97ed1a872765485b4eb9e99ec5a650/src/custom.jl#L47 +The `Base.reinterpret` function is just a helper function, you could instead write everything inside `UnROOT.interped_data`. We then builds on these, to interpret Jagged TLV branch: https://github.com/JuliaHEP/UnROOT.jl/blob/4747f6f5fd97ed1a872765485b4eb9e99ec5a650/src/custom.jl#L47 ### More details To expand a bit what we're doing here, the `rawdata` for a single `TLV` is always `64 bytes` long and the first `32 bytes` are TObject header which we don't care (which is why we don't care about `rawoffsets` here). The last `32 bytes` make up 4 `Float64` and we simply parse them and return a collection of (julia) `LorentzVector{Float64}`. diff --git a/test/samples/issue61.py b/test/samples/issue61.py index a19d5147..e26d4ca7 100644 --- a/test/samples/issue61.py +++ b/test/samples/issue61.py @@ -1,6 +1,6 @@ import ROOT as r -# https://github.com/tamasgal/UnROOT.jl/issues/61 +# https://github.com/JuliaHEP/UnROOT.jl/issues/61 # needs to be run in a specific environment to trigger the issue # in the first place From b05997fb259a06733541833c171a8484f9133d9b Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Thu, 7 Apr 2022 11:46:32 -0400 Subject: [PATCH 48/59] add TLeafS (#161) --- src/bootstrap.jl | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/bootstrap.jl b/src/bootstrap.jl index ea1b3feb..3c2e72a6 100644 --- a/src/bootstrap.jl +++ b/src/bootstrap.jl @@ -202,6 +202,41 @@ end primitivetype(l::TLeafI) = l.fIsUnsigned ? UInt32 : Int32 +# FIXME this should be generated and inherited from TLeaf +@with_kw struct TLeafS + # from TNamed + fName + fTitle + + # from TLeaf + fLen + fLenType + fOffset + fIsRange + fIsUnsigned + fLeafCount + + # own fields + fMinimum + fMaximum +end + +function parsefields!(io, fields, T::Type{TLeafS}) + preamble = Preamble(io, T) + parsefields!(io, fields, TLeaf) + fields[:fMinimum] = readtype(io, Int16) + fields[:fMaximum] = readtype(io, Int16) + endcheck(io, preamble) +end + +function unpack(io, tkey::TKey, refs::Dict{Int32, Any}, T::Type{TLeafS}) + @initparse + parsefields!(io, fields, T) + T(;fields...) +end + +primitivetype(l::TLeafS) = l.fIsUnsigned ? UInt16 : Int16 + # FIXME this should be generated and inherited from TLeaf @with_kw struct TLeafL # from TNamed From e5746a002b52f9ff6e3793dd37b595851b4f8e6a Mon Sep 17 00:00:00 2001 From: Tamas Gal Date: Fri, 8 Apr 2022 11:02:53 +0200 Subject: [PATCH 49/59] Change the URL of deploydocs to the new one --- docs/make.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index bb4837a7..c863d999 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -23,5 +23,5 @@ makedocs(; ) deploydocs(; - repo="github.com/tamasgal/UnROOT.jl", + repo="github.com/JulieHEP/UnROOT.jl", ) From 494f19186a09559e426ab7af55c58837b5e2e5d4 Mon Sep 17 00:00:00 2001 From: Tamas Gal Date: Mon, 25 Apr 2022 09:51:23 +0100 Subject: [PATCH 50/59] Fix error when file is not existent (#164) * Fix error when file is not existent * Use SystemError instead of general error * Add test for non-existent-file opening --- src/root.jl | 2 +- test/runtests.jl | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/root.jl b/src/root.jl index bbeff6e4..2f107d51 100644 --- a/src/root.jl +++ b/src/root.jl @@ -60,7 +60,7 @@ function ROOTFile(filename::AbstractString; customstructs = Dict("TLorentzVector fobj = if startswith(filename, r"https?://") HTTPStream(filename) else - !isfile(filename) && "$filename is not a file" + !isfile(filename) && throw(SystemError("opening file $filename", 2)) MmapStream(filename) end header_bytes = read(fobj, HEAD_BUFFER_SIZE) diff --git a/test/runtests.jl b/test/runtests.jl index d10663eb..93ca152a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -91,6 +91,8 @@ end @testset "ROOTFile" begin + @test_throws SystemError ROOTFile("non_existent_fname.root") + ROOTFile(joinpath(SAMPLES_DIR, "tree_with_histos.root")) do rootfile @test 100 == rootfile.header.fBEGIN @test 1 == length(rootfile.directory.keys) From 353b5b0410926b6cd06748deccd4cd2e568c8a32 Mon Sep 17 00:00:00 2001 From: Tamas Gal Date: Mon, 25 Apr 2022 10:52:06 +0200 Subject: [PATCH 51/59] Bump version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 5e156837..9ecbb10a 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnROOT" uuid = "3cd96dde-e98d-4713-81e9-a4a1b0235ce9" authors = ["Tamas Gal", "Jerry Ling", "Johannes Schumann", "Nick Amin"] -version = "0.8.5" +version = "0.8.6" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 9b42eb886447f1f3f1a045127cd53ab5f57e2033 Mon Sep 17 00:00:00 2001 From: Tamas Gal Date: Fri, 29 Apr 2022 16:46:17 +0200 Subject: [PATCH 52/59] C-style arrays (#166) * Add samples for issue 165 * fix C-stype array * fix io Co-authored-by: Jerry Ling --- src/custom.jl | 22 ++++++++++++++++++++ src/displays.jl | 2 +- src/root.jl | 5 ++++- test/runtests.jl | 14 +++++++++++++ test/samples/issue165.root | Bin 0 -> 5758 bytes test/samples/issue165_multiple_baskets.root | Bin 0 -> 7068 bytes 6 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 test/samples/issue165.root create mode 100644 test/samples/issue165_multiple_baskets.root diff --git a/src/custom.jl b/src/custom.jl index e69da402..6668d05d 100644 --- a/src/custom.jl +++ b/src/custom.jl @@ -31,6 +31,28 @@ end # Custom struct interpretation abstract type CustomROOTStruct end +struct FixLenVector{N, T} <: AbstractVector{T} + vec::SVector{N, T} +end +(::Type{FixLenVector{N, T}})() where {N, T} = FixLenVector(zero(SVector{N, T})) +Base.length(x::FixLenVector) = length(x.vec) +Base.length(::Type{FixLenVector{N, T}}) where {N, T} = N +Base.size(x::FixLenVector) = size(x.vec) +Base.eltype(x::FixLenVector) = eltype(x.vec) +Base.iterate(x::FixLenVector) = iterate(x.vec) +Base.iterate(x::FixLenVector, n) = iterate(x.vec, n) +Base.getindex(x::FixLenVector, n) = getindex(x.vec, n) +function Base.reinterpret(::Type{FixLenVector{N, T}}, v::AbstractVector{UInt8}) where {N, T} + vs = reinterpret(T, v) + @. vs = ntoh(vs) + FixLenVector(SVector{N, T}(vs)) +end +function interped_data(rawdata, rawoffsets, ::Type{T}, ::Type{Nojagg}) where {T <: FixLenVector} + n = sizeof(T) + [ + reinterpret(T, x) for x in Base.Iterators.partition(rawdata, n) + ] +end # TLorentzVector const LVF64 = LorentzVector{Float64} diff --git a/src/displays.jl b/src/displays.jl index ce1335fa..ae143a38 100644 --- a/src/displays.jl +++ b/src/displays.jl @@ -79,7 +79,7 @@ end function Base.show(io::IO, ::MIME"text/plain", br::LazyBranch) print(io, summary(br)) - println(": ") + println(io, ": ") if length(br) < 200 Base.print_array(IOContext(io, :limit => true), br[:]) else diff --git a/src/root.jl b/src/root.jl index 2f107d51..b5a11293 100644 --- a/src/root.jl +++ b/src/root.jl @@ -395,9 +395,12 @@ function auto_T_JaggT(f::ROOTFile, branch; customstructs::Dict{String, Type}) end else _type = primitivetype(leaf) + if leaf.fLen > 1 # treat NTuple as Nojagg since size is static + _type = FixLenVector{Int(leaf.fLen), _type} + return _type, Nojagg + end _type = _jaggtype === Nojagg ? _type : Vector{_type} end - return _type, _jaggtype end diff --git a/test/runtests.jl b/test/runtests.jl index 93ca152a..9b27eb4e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -708,6 +708,20 @@ end @test all(onesrow .== 1) end +@testset "C-array types" begin + tree = LazyTree(UnROOT.samplefile("issue165_multiple_baskets.root"), "arrays") + ele = tree.carr[3] + @test length(tree.carr) == 3 + @test length(ele) == 9 + @test eltype(ele) == Float64 + @test length(typeof(ele)) == 9 + @test all(ele .≈ + [0.7775048011809144, 0.8664217530127716, 0.4918492038230641, + 0.24464299401484568, 0.38991686533667, 0.15690925771226608, + 0.3850047958013624, 0.9268160513261408, 0.9298329730191421]) + @test all(ele .== [ele...]) +end + @testset "basketarray_iter()" begin f = UnROOT.samplefile("tree_with_vector_multiple_baskets.root") t = LazyTree(f,"t1") diff --git a/test/samples/issue165.root b/test/samples/issue165.root new file mode 100644 index 0000000000000000000000000000000000000000..298fc3ba1bef300d6acfa35874586ffcf8d3d3d4 GIT binary patch literal 5758 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 Wr5Jts|EG%)=*j_=ubWUT=wASk)J01G literal 0 HcmV?d00001 diff --git a/test/samples/issue165_multiple_baskets.root b/test/samples/issue165_multiple_baskets.root new file mode 100644 index 0000000000000000000000000000000000000000..24e78bff85b21da42e50a2e1c39ca064116307eb GIT binary patch literal 7068 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 IK#=(V14cEX-~a#s literal 0 HcmV?d00001 From 5a70053b7176fc5f9d3482ef62ee0cb93ca6bdc3 Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Fri, 29 Apr 2022 10:46:40 -0400 Subject: [PATCH 53/59] Update Project.toml bump version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 9ecbb10a..5e32880b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnROOT" uuid = "3cd96dde-e98d-4713-81e9-a4a1b0235ce9" authors = ["Tamas Gal", "Jerry Ling", "Johannes Schumann", "Nick Amin"] -version = "0.8.6" +version = "0.8.7" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 44e1351fcee632a983bcc20b1543036f692b5e0a Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Thu, 19 May 2022 15:21:23 -0400 Subject: [PATCH 54/59] Normalize branch name (#156) * normalize branch alias * touch up docs [skipci] --- Project.toml | 2 +- src/iteration.jl | 18 +++++++++++++++++- test/runtests.jl | 10 +++++----- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Project.toml b/Project.toml index 5e32880b..bace3bf8 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "UnROOT" uuid = "3cd96dde-e98d-4713-81e9-a4a1b0235ce9" authors = ["Tamas Gal", "Jerry Ling", "Johannes Schumann", "Nick Amin"] -version = "0.8.7" +version = "0.8.8" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" diff --git a/src/iteration.jl b/src/iteration.jl index e6437aeb..bc93038d 100644 --- a/src/iteration.jl +++ b/src/iteration.jl @@ -279,6 +279,10 @@ This means that at any given time only `N` baskets are cached, where `N` is the !!! note Accessing with `[start:stop]` will return a `LazyTree` with concrete internal table. +!!! warning + Split branches are re-named, and the exact renaming may change. See + [Issue 156](https://github.com/JuliaHEP/UnROOT.jl/pull/156) for context. + # Example ```julia julia> mytree = LazyTree(f, "Events", ["Electron_dxy", "nMuon", r"Muon_(pt|eta)\$"]) @@ -309,7 +313,19 @@ function LazyTree(f::ROOTFile, s::AbstractString, branches) end end for b in res_bnames - d[Symbol(b)] = f["$s/$b"] + # split by `.` or `/` + norm_name = b + v = split(b, r"\.|\/") + if length(v) >= 2 # only normalize name when branches are split + head = v[1] + tail = v[2:end] + # remove duplicated info + replace!(tail, head => "") + # remove known split branch information + replace!(tail, "fCoordinates" => "") + norm_name = join([head; join(tail)], "_") + end + d[Symbol(norm_name)] = f["$s/$b"] end return LazyTree(TypedTables.Table(d)) end diff --git a/test/runtests.jl b/test/runtests.jl index 9b27eb4e..82eb6b1e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -528,16 +528,16 @@ end # Issues @testset "issues" begin - rootfile = ROOTFile(joinpath(SAMPLES_DIR, "issue7.root")) + rootfile = UnROOT.samplefile("issue7.root") @test 2 == length(keys(rootfile)) @test [1.0, 2.0, 3.0] == UnROOT.array(rootfile, "TreeD/nums") @test [1.0, 2.0, 3.0] == UnROOT.array(rootfile, "TreeF/nums") close(rootfile) - # issue 55 - rootfile = ROOTFile(joinpath(SAMPLES_DIR, "cms_ntuple_wjet.root")) + # issue #55 and #156 + rootfile = UnROOT.samplefile("cms_ntuple_wjet.root") pts1 = UnROOT.array(rootfile, "variable/met_p4/fCoordinates/fCoordinates.fPt"; raw=false) - pts2 = LazyTree(rootfile, "variable", [r"met_p4/fCoordinates/.*", "mll"])[!, Symbol("met_p4/fCoordinates/fCoordinates.fPt")] + pts2 = LazyTree(rootfile, "variable", [r"met_p4/fCoordinates/.*", "mll"])[!, Symbol("met_p4_fPt")] pts3 = rootfile["variable/good_jets_p4/good_jets_p4.fCoordinates.fPt"] @test 24 == length(pts1) @test Float32[69.96958, 25.149912, 131.66693, 150.56802] == pts1[1:4] @@ -546,7 +546,7 @@ end close(rootfile) # issue 61 - rootfile = ROOTFile(joinpath(SAMPLES_DIR, "issue61.root")) + rootfile = UnROOT.samplefile("issue61.root") @test rootfile["Events/Jet_pt"][:] == Vector{Float32}[[], [27.324587, 24.889547, 20.853024], [], [20.33066], [], []] close(rootfile) From 23d87edc7cb984a4dfc94d1733bccaead1a0770b Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Wed, 1 Jun 2022 11:28:14 -0500 Subject: [PATCH 55/59] xrootd read support (#150) * xrootd * bump Julia requirement to >=1.6 --- .github/workflows/ci.yml | 3 --- Project.toml | 4 ++- src/root.jl | 5 ++++ src/streamsource.jl | 57 ++++++++++++++++++++++++++++++++++++++-- test/runtests.jl | 4 +++ 5 files changed, 67 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5dcc9416..f6686aa3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,9 +15,6 @@ jobs: fail-fast: false matrix: version: - - '1.3' - - '1.4' - - '1.5' - '1.6' - '1' # Leave this line unchanged. '1' will automatically expand to the latest stable 1.x release of Julia. - 'nightly' diff --git a/Project.toml b/Project.toml index bace3bf8..490ab880 100644 --- a/Project.toml +++ b/Project.toml @@ -23,6 +23,7 @@ PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" TypedTables = "9d95f2ec-7b3d-5a63-8d20-e2491e220bb9" +xrootdgo_jll = "9d84c17e-11f2-50ef-8cc9-e9701362097f" [compat] AbstractTrees = "^0.3.0" @@ -44,7 +45,8 @@ PrettyTables = "^1.2.0" StaticArrays = "^0.12.0, ^1" Tables = "^1.0.0" TypedTables = "^1.0.0" -julia = "^1.3" +julia = "^1.6" +xrootdgo_jll = "^0.31.1" [extras] InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" diff --git a/src/root.jl b/src/root.jl index b5a11293..765c9160 100644 --- a/src/root.jl +++ b/src/root.jl @@ -59,6 +59,11 @@ const HEAD_BUFFER_SIZE = 1024 function ROOTFile(filename::AbstractString; customstructs = Dict("TLorentzVector" => LorentzVector{Float64})) fobj = if startswith(filename, r"https?://") HTTPStream(filename) + elseif startswith(filename, "root://") + sep_idx = findlast("//", filename) + baseurl = filename[8:first(sep_idx)-1] + filepath = filename[last(sep_idx):end] + XRDStream(baseurl, filepath, "go") else !isfile(filename) && throw(SystemError("opening file $filename", 2)) MmapStream(filename) diff --git a/src/streamsource.jl b/src/streamsource.jl index 78935cf6..8ac211b7 100644 --- a/src/streamsource.jl +++ b/src/streamsource.jl @@ -1,5 +1,12 @@ +using xrootdgo_jll import HTTP +mutable struct XRDStream + gofile_id::Cstring # used as key to a global `map` on the Go side + seekloc::Int + size::Int +end + mutable struct MmapStream # Mmap based mmap_ary::Vector{UInt8} seekloc::Int @@ -75,10 +82,10 @@ mutable struct HTTPStream end end -const SourceStream = Union{MmapStream, HTTPStream} +const SourceStream = Union{MmapStream, HTTPStream, XRDStream} function Base.read(fobj::SourceStream, ::Type{T}) where T - return reinterpret(T, read(fobj, sizeof(T)))[1] # TODO: use `only` + return only(reinterpret(T, read(fobj, sizeof(T)))) end function Base.position(fobj::SourceStream) @@ -121,3 +128,49 @@ end function Base.read(fobj::SourceStream) read(fobj, fobj.size - fobj.seekloc + 1) end + +function XRDStream(urlbase::AbstractString, filepath::AbstractString, username::AbstractString) + file_id = @ccall xrootdgo.Open(urlbase::Cstring, filepath::Cstring, username::Cstring)::Cstring + # file_id = @threadcall((:Open, xrootdgo), Cstring, (Cstring, Cstring, Cstring), urlbase, filepath, username) + size = @ccall xrootdgo.Size(file_id::Cstring)::Int + XRDStream(file_id, 0, size) +end + +function Base.close(fobj::XRDStream) + xrootdgo.Close(fobj.gofile_id) +end + +function read_seek_nb(fobj::XRDStream, seek, nb) + buffer = Vector{UInt8}(undef, nb) + @threadcall((:ReadAt, xrootdgo), Cvoid, (Ptr{UInt8}, Cstring, Clong, Clong), buffer, fobj.gofile_id, nb, seek) + # @ccall xrootdgo.ReadAt(buffer::Ptr{UInt8}, + # fobj.gofile_id::Cstring, nb::Clong, seek::Clong)::Cvoid + return buffer +end +function _read!(ptr, fobj, nb, seekloc) + @ccall xrootdgo.ReadAt(ptr::Ptr{UInt8}, + fobj.gofile_id::Cstring, nb::Clong, seekloc::Clong)::Cvoid +end + +function _read!(ptr, fobj, nb) + _read!(ptr, fobj, nb, fobj.seekloc) +end + +function Base.read(fobj::XRDStream, ::Type{T}) where T + @debug @show T, sizeof(T) + nb = sizeof(T) + output = Ref{T}() + tko = Base.@_gc_preserve_begin output + po = Ptr{UInt8}(pointer_from_objref(output)) + _read!(po, fobj, nb, fobj.seekloc) + Base.@_gc_preserve_end tko + fobj.seekloc += nb + return output[] +end + +function Base.read(fobj::XRDStream, nb::Integer) + buffer = Vector{UInt8}(undef, nb) + GC.@preserve buffer _read!(buffer, fobj, nb, fobj.seekloc) + fobj.seekloc += nb + return buffer +end diff --git a/test/runtests.jl b/test/runtests.jl index 82eb6b1e..5a29c749 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -731,6 +731,10 @@ end end @testset "SourceStream remote" begin + r = ROOTFile("root://eospublic.cern.ch//eos/root-eos/cms_opendata_2012_nanoaod/Run2012B_DoubleMuParked.root") + @test r["Events"].fEntries == 29308627 + show(devnull, r) # test display + t = LazyTree("https://scikit-hep.org/uproot3/examples/Zmumu.root", "events") @test t.eta1[1] ≈ -1.21769 @test t.eta1[end] ≈ -1.57044 From 247f65e2a4f926e6532715a66adda85d0345d3a8 Mon Sep 17 00:00:00 2001 From: Tamas Gal Date: Fri, 3 Jun 2022 19:06:23 +0200 Subject: [PATCH 56/59] JOSS Paper (#135) * Add skeleton * Add open journal PDF generator GHA * Fix path to source file * Set output path * summary and statement of need * Minor cosmetics * Add comparison intro and uproot * Add uproot reference * add functionality * add performance citation * small additions * for ci * Cite pandas and numpy * Add ROOT citation * Cleanup and some additions * Minor updates and additions * Update references * Cleanup and additions * Update conclusions with Jerry's comments * Fix typo * Add NanoAOD refernce * Add reference to CMS/NanoAOD * Fix pandas reference * Add spaces between words and references * Fix typo Co-authored-by: Jerry Ling Co-authored-by: Nick Amin --- .github/workflows/draft-pdf.yml | 23 +++++ paper/LICENSE | 21 ++++ paper/paper.bib | 143 ++++++++++++++++++++++++++ paper/paper.md | 174 ++++++++++++++++++++++++++++++++ 4 files changed, 361 insertions(+) create mode 100644 .github/workflows/draft-pdf.yml create mode 100644 paper/LICENSE create mode 100644 paper/paper.bib create mode 100644 paper/paper.md diff --git a/.github/workflows/draft-pdf.yml b/.github/workflows/draft-pdf.yml new file mode 100644 index 00000000..76310246 --- /dev/null +++ b/.github/workflows/draft-pdf.yml @@ -0,0 +1,23 @@ +on: [push] + +jobs: + paper: + runs-on: ubuntu-latest + name: Paper Draft + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Build draft PDF + uses: openjournals/openjournals-draft-action@master + with: + journal: joss + # This should be the path to the paper within your repo. + paper-path: paper/paper.md + - name: Upload + uses: actions/upload-artifact@v1 + with: + name: paper + # This is the output path where Pandoc will write the compiled + # PDF. Note, this should be the same directory as the input + # paper.md + path: paper/paper.pdf diff --git a/paper/LICENSE b/paper/LICENSE new file mode 100644 index 00000000..ffd0f5e8 --- /dev/null +++ b/paper/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Tamas Gal, Jerry Ling and Nick Amin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/paper/paper.bib b/paper/paper.bib new file mode 100644 index 00000000..b9eb5265 --- /dev/null +++ b/paper/paper.bib @@ -0,0 +1,143 @@ +@article{Julia, + author = {Bezanson, Jeff. and Edelman, Alan. and Karpinski, Stefan. and Shah, Viral B.}, + title = {Julia: A Fresh Approach to Numerical Computing}, + journal = {SIAM Review}, + volume = {59}, + number = {1}, + pages = {65-98}, + year = {2017}, + doi = {10.1137/141000671}, + URL = { https://doi.org/10.1137/141000671}, + eprint = {https://doi.org/10.1137/141000671} +} + +@article{JuliaPerformance, + title={Performance of julia for high energy physics analyses}, + author={Stanitzki, Marcel and Strube, Jan}, + journal={Computing and Software for Big Science}, + volume={5}, + number={1}, + pages={1--11}, + year={2021}, + publisher={Springer} +} + +@software{jim_pivarski_2021_5539722, + author = {Jim Pivarski and + Henry Schreiner and + Nicholas Smith and + Chris Burr and + Dmitry Kalinkin and + Giordon Stark and + Nikolai Hartmann and + Doug Davis and + Ryunosuke O'Neil and + Andrzej Novak and + Ben Greiner and + Beojan Stanislaus and + ChristopheRappold and + Cosmin Deaconu and + Daniel Cervenkov and + Jonas Rübenach and + Josh Bendavid and + Kilian Lieret and + Michele Peresano and + Raymond Ehlers and + Ruggero Turra and + Tamas Gal and + Alexander Held}, + title = {scikit-hep/uproot4: 4.1.3}, + month = sep, + year = 2021, + publisher = {Zenodo}, + version = {4.1.3}, + doi = {10.5281/zenodo.5539722}, + url = {https://doi.org/10.5281/zenodo.5539722} +} +@software{pivarski_jim_2018_6522027, + author = {Pivarski, Jim and + Osborne, Ianna and + Ifrim, Ioana and + Schreiner, Henry and + Hollands, Angus and + Biswas, Anish and + Das, Pratyush and + Roy Choudhury, Santam and + Smith, Nicholas}, + title = {Awkward Array}, + month = oct, + year = 2018, + note = {If you use this software, please cite it as below.}, + publisher = {Zenodo}, + version = {1.9.0rc4}, + doi = {10.5281/zenodo.6522027}, + url = {https://doi.org/10.5281/zenodo.6522027} +} +@Article{harris2020array, + title = {Array programming with {NumPy}}, + author = {Charles R. Harris and K. Jarrod Millman and St{\'{e}}fan J. + van der Walt and Ralf Gommers and Pauli Virtanen and David + Cournapeau and Eric Wieser and Julian Taylor and Sebastian + Berg and Nathaniel J. Smith and Robert Kern and Matti Picus + and Stephan Hoyer and Marten H. van Kerkwijk and Matthew + Brett and Allan Haldane and Jaime Fern{\'{a}}ndez del + R{\'{i}}o and Mark Wiebe and Pearu Peterson and Pierre + G{\'{e}}rard-Marchant and Kevin Sheppard and Tyler Reddy and + Warren Weckesser and Hameer Abbasi and Christoph Gohlke and + Travis E. Oliphant}, + year = {2020}, + month = sep, + journal = {Nature}, + volume = {585}, + number = {7825}, + pages = {357--362}, + doi = {10.1038/s41586-020-2649-2}, + publisher = {Springer Science and Business Media {LLC}}, + url = {https://doi.org/10.1038/s41586-020-2649-2} +} +@software{reback2020pandas, + author = {{The pandas development team}}, + title = {Pandas}, + month = feb, + year = 2020, + publisher = {Zenodo}, + version = {latest}, + doi = {10.5281/zenodo.3509134}, + url = {https://doi.org/10.5281/zenodo.3509134} +} +@article{Brun:1997pa, + author = "Brun, R. and Rademakers, F.", + editor = "Werlen, M. and Perret-Gallix, D.", + title = "{ROOT: An object oriented data analysis framework}", + doi = "10.1016/S0168-9002(97)00048-X", + journal = "Nucl. Instrum. Meth. A", + volume = "389", + pages = "81--86", + year = "1997" +} + +@article{Adri_n_Mart_nez_2016, + doi = {10.1088/0954-3899/43/8/084001}, + url = {https://doi.org/10.1088%2F0954-3899%2F43%2F8%2F084001}, + year = 2016, + month = {jun}, + publisher = {{IOP} Publishing}, + volume = {43}, + number = {8}, + pages = {084001}, + author = {S Adri{\'{a} +}n-Mart{\'{\i}}nez and M Ageron and F Aharonian and S Aiello and A Albert and F Ameli and E Anassontzis and M Andre and G Androulakis and M Anghinolfi and G Anton and M Ardid and T Avgitas and G Barbarino and E Barbarito and B Baret and J Barrios-Mart{\'{\i}} and B Belhorma and A Belias and E Berbee and A van den Berg and V Bertin and S Beurthey and V van Beveren and N Beverini and S Biagi and A Biagioni and M Billault and M Bond{\`{\i}} and R Bormuth and B Bouhadef and G Bourlis and S Bourret and C Boutonnet and M Bouwhuis and C Bozza and R Bruijn and J Brunner and E Buis and J Busto and G Cacopardo and L Caillat and M Calamai and D Calvo and A Capone and L Caramete and S Cecchini and S Celli and C Champion and R Cherkaoui El Moursli and S Cherubini and T Chiarusi and M Circella and L Classen and R Cocimano and J A B Coelho and A Coleiro and S Colonges and R Coniglione and M Cordelli and A Cosquer and P Coyle and A Creusot and G Cuttone and A D'Amico and G De Bonis and G De Rosa and C De Sio and F Di Capua and I Di Palma and A F D{\'{\i}}az Garc{\'{\i}}a and C Distefano and C Donzaud and D Dornic and Q Dorosti-Hasankiadeh and E Drakopoulou and D Drouhin and L Drury and M Durocher and T Eberl and S Eichie and D van Eijk and I El Bojaddaini and N El Khayati and D Elsaesser and A Enzenhöfer and F Fassi and P Favali and P Fermani and G Ferrara and C Filippidis and G Frascadore and L A Fusco and T Gal and S Galat{\`{a}} and F Garufi and P Gay and M Gebyehu and V Giordano and N Gizani and R Gracia and K Graf and T Gr{\'{e}}goire and G Grella and R Habel and S Hallmann and H van Haren and S Harissopulos and T Heid and A Heijboer and E Heine and S Henry and J J Hern{\'{a}}ndez-Rey and M Hevinga and J Hofestädt and C M F Hugon and G Illuminati and C W James and P Jansweijer and M Jongen and M de Jong and M Kadler and O Kalekin and A Kappes and U F Katz and P Keller and G Kieft and D Kie{\ss}ling and E N Koffeman and P Kooijman and A Kouchner and V Kulikovskiy and R Lahmann and P Lamare and A Leisos and E Leonora and M Lindsey Clark and A Liolios and C D Llorens Alvarez and D Lo Presti and H Löhner and A Lonardo and M Lotze and S Loucatos and E Maccioni and K Mannheim and A Margiotta and A Marinelli and O Mari{\c{s}} and C Markou and J A Mart{\'{\i}}nez-Mora and A Martini and R Mele and K W Melis and T Michael and P Migliozzi and E Migneco and P Mijakowski and A Miraglia and C M Mollo and M Mongelli and M Morganti and A Moussa and P Musico and M Musumeci and S Navas and C A Nicolau and I Olcina and C Olivetto and A Orlando and A Papaikonomou and R Papaleo and G E P{\u{a}}v{\u{a}}la{\c{s}} and H Peek and C Pellegrino and C Perrina and M Pfutzner and P Piattelli and K Pikounis and G E Poma and V Popa and T Pradier and F Pratolongo and G Pühlhofer and S Pulvirenti and L Quinn and C Racca and F Raffaelli and N Randazzo and P Rapidis and P Razis and D Real and L Resvanis and J Reubelt and G Riccobene and C Rossi and A Rovelli and M Salda{\~{n}}a and I Salvadori and D F E Samtleben and A S{\'{a}}nchez Garc{\'{\i}}a and A S{\'{a}}nchez Losa and M Sanguineti and A Santangelo and D Santonocito and P Sapienza and F Schimmel and J Schmelling and V Sciacca and M Sedita and T Seitz and I Sgura and F Simeone and I Siotis and V Sipala and B Spisso and M Spurio and G Stavropoulos and J Steijger and S M Stellacci and D Stransky and M Taiuti and Y Tayalati and D T{\'{e}}zier and S Theraube and L Thompson and P Timmer and C Tönnis and L Trasatti and A Trovato and A Tsirigotis and S Tzamarias and E Tzamariudaki and B Vallage and V Van Elewyck and J Vermeulen and P Vicini and S Viola and D Vivolo and M Volkert and G Voulgaris and L Wiggers and J Wilms and E de Wolf and K Zachariadou and J D Zornoza and J Z{\'{u}}{\~{n}}iga}, + title = {Letter of intent for {KM}3NeT 2.0}, + journal = {Journal of Physics G: Nuclear and Particle Physics} +} +@article{Ehataht:2020ebp, + author = {Ehat\"aht, Karl}, + editor = "Doglioni, C. and Kim, D. and Stewart, G. A. and Silvestris, L. and Jackson, P. and Kamleh, W.", + collaboration = "CMS", + title = "{NANOAOD: a new compact event data format in CMS}", + doi = "10.1051/epjconf/202024506002", + journal = "EPJ Web Conf.", + volume = "245", + pages = "06002", + year = "2020" +} diff --git a/paper/paper.md b/paper/paper.md new file mode 100644 index 00000000..d9b5ede5 --- /dev/null +++ b/paper/paper.md @@ -0,0 +1,174 @@ +--- +title: 'UnROOT: an I/O library for the CERN ROOT file format written in Julia' +tags: + - Julia + - HEP +authors: + - name: Tamás Gál + orcid: 0000-0001-7821-8673 + affiliation: "1, 2" + - name: Jerry (Jiahong) Ling + orcid: 0000-0002-3359-0380 + affiliation: "3" + - name: Nick Amin + orcid: 0000-0003-2560-0013 + affiliation: "4" +affiliations: + - name: Erlangen Centre for Astroparticle Physics + index: 1 + - name: Friedrich-Alexander-Universität Erlangen-Nürnberg + index: 2 + - name: Harvard University + index: 3 + - name: University of California, Santa Barbara + index: 4 +date: 08 October 2021 +bibliography: paper.bib +--- +# Summary +`UnROOT.jl` is a pure Julia implementation of CERN ROOT [@Brun:1997pa] files I/O +(`.root`) that is fast, memory-efficient, and composes well with Julia's +high-performance iteration, array, and multi-threading interfaces. + +# Statement of need +The High-Energy Physics (HEP) community has been troubled by the two-language +problem for a long time. Often, physicists would start prototyping with a +`Python` front-end which glues to a `C/C++/Fortran` back-end. Soon they will hit +a task which is extremely hard to express in columnar (i.e. "vectorized") style, +a type of problems which are normally tackled with libraries like +`numpy` [@harris2020array] or `pandas` [@reback2020pandas]. This usually leads to +either writing `C++` kernels and interface them with `Python`, or porting the +prototype to `C++` and start to maintain two code bases including the wrapper +code. Both options are engineering challenges for physicists who usually have no +or little background in software engineering. Many steps of this process are +critical, like identifying bottlenecks, creating an architecture which is both +performant and maintainable at the same time while still being user-friendly and +logically structured. Using a `Python` front-end and dancing across language +barriers also hinders the ability to parallelize tasks that are conceptually +trivial most of the time. + +`UnROOT.jl` attempts to solve all of the above by choosing Julia, a +high-performance language with simple and expressive syntax [@Julia]. Julia is +designed to solve the two-language problem in general. This has been studied for +HEP specifically as well [@JuliaPerformance]. Analysis software written in Julia +can freely escape to a `for-loop` whenever vectorized-style processing is not +flexible enough, without any performance degradation. At the same time, +`UnROOT.jl` transparently supports multi-threading and multi-processing by +simply providing data structures which are a subtype of `AbstractArray`, the +built-in abstract type for array-like objects, which allows to interface with +array-routines from other packages easily, thanks to multiple dispatch, one of +the main features of Julia. + +# Features and Functionality + +The `ROOT` dataformat is flexible and mostly self-descriptive. Users can define +their own data structures (C++ classes) which derive from `ROOT` classes and +serialise them into directories, trees and branches. The information about the +deserialisation is written to the output file (therefore: self-descriptive) but +there are some basic structures and constants needed to bootstrap the parsing +process. One of the biggest advantages of the `ROOT` data format is the ability +to store jagged structures like nested arrays of structs with different +sub-array lengths. In high-energy physics, such structures are preferred to +resemble e.g. particle interactions and detector responses as signals from +different hardware components, combined into a tree of events. + +`UnROOT.jl` understands the core structure of `ROOT` files, and is able to +decompress and deserialize instances of the commonly used `TH1`, `TH2`, +`TDirectory`, `TTree` etc. ROOT classes. All basic C++ types for `TTree` +branches are supported as well, including their nested variants. Additionally, +`UnROOT.jl` provides a way to hook into the deserialisation process of custom +types where the automatic parsing fails. By the time of writing, `UnROOT` is +already used successfully in the data analysis of the KM3NeT neutrino +telescope [@Adri_n_Mart_nez_2016] and the CMS detector [@Ehataht:2020ebp]. + +Opening and loading a `TTree` lazily -- i.e. without reading the whole data into +memory -- is simple: + +```julia +julia> using UnROOT + +julia> f = ROOTFile("test/samples/NanoAODv5_sample.root") +ROOTFile with 2 entries and 21 streamers. +test/samples/NanoAODv5_sample.root + Events + "run" + "luminosityBlock" + "event" + "HTXS_Higgs_pt" + "HTXS_Higgs_y" + ... + +julia> mytree = LazyTree(f, "Events", ["Electron_dxy", "nMuon", r"Muon_(pt|eta)$"]) + Row Electron_dxy nMuon Muon_eta Muon_pt + Vector{Float32} UInt32 Vector{Float32} Vector{Float32} + + 1 [0.000371] 0 [] [] + 2 [-0.00982] 2 [0.53, 0.229] [19.9, 15.3] + 3 [] 0 [] [] + 4 [-0.00157] 0 [] [] + ... +``` + +As seen in the above example, the entries in the columns are multi-dimensional +and jagged. The `LazyTree` object acts as a table which suports sequential +or parallel iteration, selections and filtering based on ranges or masks, and +operations on whole columns: + +```julia +for event in mytree + # ... Operate on event +end + +Threads.@threads for event in mytree # multi-threading + # ... Operate on event +end + +mytree.Muon_pt # a column as a lazy vector of vectors +``` + +The `LazyTree` is designed as `<: AbstractArray` which makes it compose well +with the rest of the Julia ecosystem. For example, syntactic loop fusion [^1] or +Query-style tabular manipulations provided by packages like `Query.jl` [^2] without +any additional code support just work out-of-the-box. + +# Comparison with existing software + +This section focusses on the comparison with other existing ROOT I/O solutions +in the Julia universe, however, one honorable mention is `uproot` +[@jim_pivarski_2021_5539722], which is a purely Python-based ROOT I/O library +and played (still plays) an important role for the development of `UnROOT.jl` as +it is by the time of writing the most complete and best documented ROOT I/O +implementation. + +- `UpROOT.jl` is a wrapper for the aforementioned `uproot` Python package and + uses `PyCall.jl` [^3] as a bridge, which means that it relies on `Python` as a + glue language. In addition to that, `uproot` itself utilises the C++ library + `AwkwardArray` [@pivarski_jim_2018_6522027] to efficiently deal with jagged + data in `ROOT` files. Most of the features of `uproot` are available in the + Julia context, but there are intrinsic performance and usability drawbacks due + to the three language architecture. + +- `ROOT.jl` [^4] is one of the oldest Julia `ROOT` packages. It uses C++ bindings to + directly wrap the `ROOT` framework and therefore is not limited ot I/O. + Unfortunately, the `Cxx.jl` [^5] package which is used to generate the C++ glue + code does not support Julia 1.4 or later. The multi-threaded features are also + limited. + +# Conclusion + +`UnROOT.jl` is an important package in high-energy physics and related +scientific fields where the `ROOT` dataformat is established, since the ability +to read and parse scientific data is certainly the first mandatory step to open +the window to a programming language and its package ecosystem. `UnROOT.jl` has +demonstrated tree processing speeds at the same level as the `C++` `ROOT` +framework in per-event iteration as well as the Python-based `uproot` library in +chunked iteration. + +# References + + +[^1]: https://julialang.org/blog/2017/01/moredots/ +[^2]: https://github.com/queryverse/Query.jl +[^3]: https://github.com/JuliaPy/PyCall.jl +[^4]: https://github.com/JuliaHEP/ROOT.jl +[^5]: https://github.com/JuliaInterop/Cxx.jl From 54aea8b04ab1040ba93c3373d8e9a8c8f2493f40 Mon Sep 17 00:00:00 2001 From: Moelf Date: Fri, 3 Jun 2022 14:39:03 -0400 Subject: [PATCH 57/59] add DOI --- paper/paper.bib | 1 + 1 file changed, 1 insertion(+) diff --git a/paper/paper.bib b/paper/paper.bib index b9eb5265..4485c99b 100644 --- a/paper/paper.bib +++ b/paper/paper.bib @@ -17,6 +17,7 @@ @article{JuliaPerformance journal={Computing and Software for Big Science}, volume={5}, number={1}, + doi = {10.1007/s41781-021-00053-3}, pages={1--11}, year={2021}, publisher={Springer} From 9358bd596b2f3673a0324172b90870f8348902ca Mon Sep 17 00:00:00 2001 From: Jerry Ling Date: Fri, 3 Jun 2022 16:26:51 -0400 Subject: [PATCH 58/59] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4b32be80..ab538115 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-) +[![status](https://joss.theoj.org/papers/bab42b0c60f9dc7ef3b8d6460bc7229c/status.svg)](https://joss.theoj.org/papers/bab42b0c60f9dc7ef3b8d6460bc7229c) [![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://juliahep.github.io/UnROOT.jl/dev) [![Build Status](https://github.com/JuliaHEP/UnROOT.jl/workflows/CI/badge.svg)](https://github.com/JuliaHEP/UnROOT.jl/actions) [![Codecov](https://codecov.io/gh/JuliaHEP/UnROOT.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/JuliaHEP/UnROOT.jl) From fe28a4483236ea58cc43fefdad620e356c4bb674 Mon Sep 17 00:00:00 2001 From: Tamas Gal Date: Fri, 3 Jun 2022 23:23:01 +0200 Subject: [PATCH 59/59] Add JOSS badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ab538115..77475c30 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://juliahep.github.io/UnROOT.jl/dev) [![Build Status](https://github.com/JuliaHEP/UnROOT.jl/workflows/CI/badge.svg)](https://github.com/JuliaHEP/UnROOT.jl/actions) [![Codecov](https://codecov.io/gh/JuliaHEP/UnROOT.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/JuliaHEP/UnROOT.jl) +[![status](https://joss.theoj.org/papers/bab42b0c60f9dc7ef3b8d6460bc7229c/status.svg)](https://joss.theoj.org/papers/bab42b0c60f9dc7ef3b8d6460bc7229c) UnROOT.jl is a reader for the [CERN ROOT](https://root.cern) file format written entirely in Julia, without any dependence on ROOT or Python.

Tamas Gal

💻 📖 🚇 🔣 ⚠️

Jerry Ling

💻 ⚠️ 🔣 📖

Johannes Schumann

💻 ⚠️ 🔣

Nick Amin

💻 ⚠️ 🔣

Tamas Gal

💻 📖 🚇 🔣 ⚠️

Jerry Ling

💻 ⚠️ 🔣 📖

Johannes Schumann

💻 ⚠️ 🔣

Nick Amin

💻 ⚠️ 🔣

Mosè Giordano

🚇

Oliver Schulz

🤔

Misha Mikhasenko

🔣