Skip to content

Commit 63faefa

Browse files
Exclusive jet selections (#47)
* Exclusive jets base on njets First implementation of exclusive jet finder, for njets Add a record of initial jets/clusters given to the ClusterSequence. Add an Enum for the jet algorithm (to be allowed as a CLI option and also to record this value internally in the ClusterSequence). * Introduce algorithm enum Used to store the algorithm in the ClusterSequence Can also be used to build a simple CLI to select the algorithm * Refactor enum for strategy name Call this RecoStrategy instead if JetRecoStrategy (This is different from JetAlgorithm, where it's how do I define jets, vs. RecoStrategy, how to I build the reconstructed jets - seems to make sense to me...) * Implement n_exclusive_jets(cs, dcut) This returns the number of jets that would pass a cut where the reconstruction stopped at a certain dcut value * Sort final jets by pt This makes comparisons a lot easier! * Correct normalisation of dij values Had been multiplying by R^2, instead of dividing! * Add first inclusive selection test Inclusive kT with dijmax=20 * More exclusive tests Better structuring for exclusive selection tests using a struct for all parameters. Tests for njets and dijcut for both Cambridge/Aachen and Inclusive-kT.
1 parent 6a4fb22 commit 63faefa

17 files changed

+95017
-80
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ The algorithms include anti-${k}_\text{T}$, Cambridge/Aachen and inclusive $k_\t
1717
The simplest interface is to call:
1818

1919
```julia
20-
cs = jet_reconstruct(particles::Vector{T}; p = -1, R = 1.0, recombine = +, strategy = JetRecoStrategy.Best)
20+
cs = jet_reconstruct(particles::Vector{T}; p = -1, R = 1.0, recombine = +, strategy = RecoStrategy.Best)
2121
```
2222

2323
- `particles` - a vector of input particles for the clustering
@@ -30,7 +30,7 @@ cs = jet_reconstruct(particles::Vector{T}; p = -1, R = 1.0, recombine = +, strat
3030
- `1` gives inclusive $k_\text{T}$
3131
- `R` - the cone size parameter; no particles more geometrically distance than `R` will be merged (default 1.0)
3232
- `recombine` - the function used to merge two pseudojets (default is a simple 4-vector addition of $`(E, \mathbf{p})`$)
33-
- `strategy` - the algorithm strategy to adopt, as described below (default `JetRecoStrategy.Best`)
33+
- `strategy` - the algorithm strategy to adopt, as described below (default `RecoStrategy.Best`)
3434

3535
The object returned is a `ClusterSequence`, which internally tracks all merge steps.
3636

@@ -56,9 +56,9 @@ Three strategies are available for the different algorithms:
5656

5757
| Strategy Name | Notes | Interface |
5858
|---|---|---|
59-
| `JetRecoStrategy.Best` | Dynamically switch strategy based on input particle density | `jet_reconstruct` |
60-
| `JetRecoStrategy.N2Plain` | Global matching of particles at each interation (works well for low $N$) | `plain_jet_reconstruct` |
61-
| `JetRecoStrategy.N2Tiled` | Use tiles of radius $R$ to limit search space (works well for higher $N$) | `tiled_jet_reconstruct` |
59+
| `RecoStrategy.Best` | Dynamically switch strategy based on input particle density | `jet_reconstruct` |
60+
| `RecoStrategy.N2Plain` | Global matching of particles at each interation (works well for low $N$) | `plain_jet_reconstruct` |
61+
| `RecoStrategy.N2Tiled` | Use tiles of radius $R$ to limit search space (works well for higher $N$) | `tiled_jet_reconstruct` |
6262

6363
Generally one can use the `jet_reconstruct` interface, shown above, as the *Best* strategy safely as the overhead is extremely low. That interface supports a `strategy` option to switch to a different option.
6464

examples/jetreco.jl

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ using JSON
1717
using LorentzVectorHEP
1818
using JetReconstruction
1919

20-
function profile_code(profile, jet_reconstruction, events, niters; R=0.4, p=-1, strategy=JetRecoStrategy.N2Tiled)
20+
function profile_code(profile, jet_reconstruction, events, niters; R=0.4, p=-1, strategy=RecoStrategy.N2Tiled)
2121
Profile.init(n = 5*10^6, delay = 0.00001)
2222
profile_events(events) = begin
2323
for evt in events
@@ -67,10 +67,12 @@ serialising the reconstructed jet outputs.
6767
"""
6868
function jet_process(
6969
events::Vector{Vector{PseudoJet}};
70-
ptmin::Float64 = 5.0,
71-
distance::Float64 = 0.4,
70+
distance::Real = 0.4,
7271
power::Integer = -1,
73-
strategy::JetRecoStrategy.Strategy,
72+
ptmin::Real = 5.0,
73+
dcut = nothing,
74+
njets = nothing,
75+
strategy::RecoStrategy.Strategy,
7476
nsamples::Integer = 1,
7577
gcoff::Bool = false,
7678
profile = nothing,
@@ -114,11 +116,18 @@ function jet_process(
114116
gcoff && GC.enable(false)
115117
t_start = time_ns()
116118
for (ievt, event) in enumerate(events)
117-
finaljets = inclusive_jets(jet_reconstruct(event, R = distance, p = power, strategy = strategy), ptmin)
119+
if !isnothing(njets)
120+
finaljets = exclusive_jets(jet_reconstruct(event, R = distance, p = power, strategy = strategy), njets=njets)
121+
elseif !isnothing(dcut)
122+
finaljets = exclusive_jets(jet_reconstruct(event, R = distance, p = power, strategy = strategy), dcut=dcut)
123+
else
124+
finaljets = inclusive_jets(jet_reconstruct(event, R = distance, p = power, strategy = strategy), ptmin)
125+
end
118126
# Only print the jet content once
119127
if irun == 1
120128
@info begin
121129
jet_output = "Event $(ievt)\n"
130+
sort!(finaljets, by = x -> pt(x), rev=true)
122131
for (ijet, jet) in enumerate(finaljets)
123132
jet_output *= " $(ijet) - $(jet)\n"
124133
end
@@ -182,10 +191,18 @@ parse_command_line(args) = begin
182191
default = 0
183192

184193
"--ptmin"
185-
help = "Minimum p_t for final jets (GeV)"
194+
help = "Minimum p_t for final inclusive jets (energy unit is the same as the input clusters, usually GeV)"
186195
arg_type = Float64
187196
default = 5.0
188197

198+
"--exclusive-dcut"
199+
help = "Return all exclusive jets where further merging would have d>d_cut"
200+
arg_type = Float64
201+
202+
"--exclusive-njets"
203+
help = "Return all exclusive jets once clusterisation has produced n jets"
204+
arg_type = Int
205+
189206
"--distance", "-R"
190207
help = "Distance parameter for jet merging"
191208
arg_type = Float64
@@ -198,8 +215,8 @@ parse_command_line(args) = begin
198215

199216
"--strategy"
200217
help = """Strategy for the algorithm, valid values: $(join(JetReconstruction.AllJetRecoStrategies, ", "))"""
201-
arg_type = JetRecoStrategy.Strategy
202-
default = JetRecoStrategy.Best
218+
arg_type = RecoStrategy.Strategy
219+
default = RecoStrategy.Best
203220

204221
"--nsamples", "-m"
205222
help = "Number of measurement points to acquire."
@@ -238,8 +255,8 @@ parse_command_line(args) = begin
238255
end
239256

240257

241-
function ArgParse.parse_item(::Type{JetRecoStrategy.Strategy}, x::AbstractString)
242-
s = tryparse(JetRecoStrategy.Strategy, x)
258+
function ArgParse.parse_item(::Type{RecoStrategy.Strategy}, x::AbstractString)
259+
s = tryparse(RecoStrategy.Strategy, x)
243260
if s === nothing
244261
throw(ErrorException("Invalid value for strategy: $(x)"))
245262
end
@@ -258,8 +275,8 @@ main() = begin
258275
global_logger(logger)
259276
events::Vector{Vector{PseudoJet}} =
260277
read_final_state_particles(args[:file], maxevents = args[:maxevents], skipevents = args[:skip])
261-
jet_process(events, ptmin = args[:ptmin], distance = args[:distance],
262-
power = args[:power], strategy = args[:strategy],
278+
jet_process(events, distance = args[:distance], power = args[:power], strategy = args[:strategy],
279+
ptmin = args[:ptmin], dcut = args[:exclusive_dcut], njets = args[:exclusive_njets],
263280
nsamples = args[:nsamples], gcoff = args[:gcoff], profile = args[:profile],
264281
alloc = args[:alloc], dump = args[:dump])
265282
nothing

src/AlgorithmStrategyEnums.jl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# EnumX provides scoped enums, which are nicer
2+
using EnumX
3+
4+
# Valid strategy enum
5+
@enumx T = Strategy RecoStrategy Best N2Plain N2Tiled
6+
const AllJetRecoStrategies = [String(Symbol(x)) for x in instances(RecoStrategy.Strategy)]
7+
8+
# Algorithm emun
9+
@enumx T = Algorithm JetAlgorithm AntiKt Cambridge Kt EEKt Durham
10+
const AllJetRecoAlgorithms = [String(Symbol(x)) for x in instances(JetAlgorithm.Algorithm)]
11+
12+
# Map from power values to algorithms
13+
power2algorithm = Dict(-1 => JetAlgorithm.AntiKt,
14+
0 => JetAlgorithm.Cambridge,
15+
1 => JetAlgorithm.Kt)
16+
17+
# Map from string to an enum value (used for CLI parsing)
18+
Base.tryparse(E::Type{<:Enum}, str::String) =
19+
let insts = instances(E),
20+
p = findfirst(==(Symbol(str)) Symbol, insts)
21+
22+
p !== nothing ? insts[p] : nothing
23+
end

src/ClusterSequence.jl

Lines changed: 111 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
# ported to this package by Graeme Stewart
55

66
using Accessors
7+
using Logging
78

89
# One cannot use an ENUM here as it's a different type
910
# I don't know any good way to keep this lean and to have these necessary
1011
# flags other than using these "magic numbers"
11-
const Invalid=-3
12-
const NonexistentParent=-2
13-
const BeamJet=-1
12+
# (TODO: this is not runtime critical, so could consider a Union{Int,Emum}?)
13+
const Invalid = -3
14+
const NonexistentParent = -2
15+
const BeamJet = -1
1416

1517
"""A struct holding a record of jet mergers and finalisations"""
1618
struct HistoryElement
@@ -58,24 +60,44 @@ Convienence structure holding all of the relevant parameters for
5860
the jet reconstruction
5961
"""
6062
struct ClusterSequence
63+
"""Algorithm and strategy used"""
64+
algorithm::JetAlgorithm.Algorithm
65+
strategy::RecoStrategy.Strategy
66+
6167
"""
6268
This contains the physical PseudoJets; for each PseudoJet one can find
63-
the corresponding position in the _history by looking at
64-
_jets[i].cluster_hist_index()
69+
the corresponding position in the history by looking at
70+
jets[i].cluster_hist_index()
6571
"""
6672
jets::Vector{PseudoJet}
6773

74+
"""
75+
Record the initial number of particlesm used for exclusive jets
76+
"""
77+
n_initial_jets::Int
78+
6879
"""
6980
This vector will contain the branching history; for each stage,
70-
history[i].jetp_index indicates where to look in the _jets
81+
history[i].jetp_index indicates where to look in the jets
7182
vector to get the physical PseudoJet.
7283
"""
7384
history::Vector{HistoryElement}
7485

7586
"""Total energy of the event"""
76-
Qtot
87+
Qtot::Any
88+
end
89+
90+
"""ClusterSequence constructor, where the power value is given"""
91+
ClusterSequence(p::Int, strategy::RecoStrategy.Strategy, jets, history, Qtot) = begin
92+
if !haskey(power2algorithm, p)
93+
raise(ArgumentError("Unrecognised algorithm for power value p=$p"))
94+
end
95+
ClusterSequence(power2algorithm[p], strategy, jets, length(jets), history, Qtot)
7796
end
7897

98+
"""ClusterSequence constructor, with direct algorithm specified"""
99+
ClusterSequence(alg::JetAlgorithm.Algorithm, strategy::RecoStrategy.Strategy, jets, history, Qtot) =
100+
ClusterSequence(alg, strategy, jets, length(jets), history, Qtot)
79101

80102
"""Add a new jet's history into the recombination sequence"""
81103
add_step_to_history!(clusterseq::ClusterSequence, parent1, parent2, jetp_index, dij) = begin
@@ -117,8 +139,8 @@ end
117139

118140
"""Return all inclusive jets of a ClusterSequence with pt > ptmin"""
119141
function inclusive_jets(clusterseq::ClusterSequence, ptmin = 0.0)
120-
dcut = ptmin * ptmin
121-
jets_local = Vector{LorentzVectorCyl}(undef, 0)
142+
pt2min = ptmin * ptmin
143+
jets_local = LorentzVectorCyl[]
122144
# sizehint!(jets_local, length(clusterseq.jets))
123145
# For inclusive jets with a plugin algorithm, we make no
124146
# assumptions about anything (relation of dij to momenta,
@@ -128,10 +150,89 @@ function inclusive_jets(clusterseq::ClusterSequence, ptmin = 0.0)
128150
elt.parent2 == BeamJet || continue
129151
iparent_jet = clusterseq.history[elt.parent1].jetp_index
130152
jet = clusterseq.jets[iparent_jet]
131-
if pt2(jet) >= dcut
153+
if pt2(jet) >= pt2min
154+
@debug "Added inclusive jet index $iparent_jet"
132155
push!(jets_local, LorentzVectorCyl(pt(jet), rapidity(jet), phi(jet), mass(jet)))
133156
end
134157
end
135158
jets_local
136159
end
137160

161+
162+
"""Return all exclusive jets of a ClusterSequence"""
163+
function exclusive_jets(clusterseq::ClusterSequence; dcut = nothing, njets = nothing)
164+
if isnothing(dcut) && isnothing(njets)
165+
throw(ArgumentError("Must pass either a dcut or an njets value"))
166+
end
167+
168+
if !isnothing(dcut)
169+
njets = n_exclusive_jets(clusterseq, dcut=dcut)
170+
end
171+
172+
# Check that an algorithm was used that makes sense for exclusive jets
173+
if !(clusterseq.algorithm (JetAlgorithm.Cambridge, JetAlgorithm.Kt, JetAlgorithm.EEKt, JetAlgorithm.Durham))
174+
throw(ArgumentError("Algorithm used is not suitable for exclusive jets ($(clusterseq.algorithm))"))
175+
end
176+
177+
# njets search
178+
stop_point = 2*clusterseq.n_initial_jets - njets + 1
179+
180+
# Sanity check - never return more jets than initial particles
181+
if stop_point < clusterseq.n_initial_jets
182+
stop_point = clusterseq.n_initial_jets
183+
end
184+
185+
# Sanity check - ensure that reconstruction proceeded to the end
186+
if 2 * clusterseq.n_initial_jets != length(clusterseq.history)
187+
throw(ErrorException("Cluster sequence is incomplete, exclusive jets unavailable"))
188+
end
189+
190+
excl_jets = LorentzVectorCyl[]
191+
for j in stop_point:length(clusterseq.history)
192+
@info "Search $j ($(clusterseq.history[j].parent1) + $(clusterseq.history[j].parent2))"
193+
for parent in (clusterseq.history[j].parent1, clusterseq.history[j].parent2)
194+
if (parent < stop_point && parent > 0)
195+
@info "Added exclusive jet index $(clusterseq.history[parent].jetp_index)"
196+
jet = clusterseq.jets[clusterseq.history[parent].jetp_index]
197+
push!(excl_jets, LorentzVectorCyl(pt(jet), rapidity(jet), phi(jet), mass(jet)))
198+
end
199+
end
200+
end
201+
202+
excl_jets
203+
end
204+
205+
206+
"""Return all number of exclusive jets of a ClusterSequence that are above a certain dcut value"""
207+
function n_exclusive_jets(clusterseq::ClusterSequence; dcut::AbstractFloat)
208+
# Check that an algorithm was used that makes sense for exclusive jets
209+
if !(clusterseq.algorithm (JetAlgorithm.Cambridge, JetAlgorithm.Kt, JetAlgorithm.EEKt, JetAlgorithm.Durham))
210+
throw(ArgumentError("Algorithm used is not suitable for exclusive jets ($(clusterseq.algorithm))"))
211+
end
212+
213+
# Locate the point where clustering would have stopped (i.e. the
214+
# first time max_dij_so_far > dcut)
215+
i_dcut = length(clusterseq.history)
216+
for i_history length(clusterseq.history):-1:1
217+
@info "Examining $i_history, max_dij=$(clusterseq.history[i_history].max_dij_so_far)"
218+
if clusterseq.history[i_history].max_dij_so_far <= dcut
219+
i_dcut = i_history
220+
break
221+
end
222+
end
223+
224+
# The number of jets is then given by this formula
225+
length(clusterseq.history) - i_dcut
226+
227+
# int i = _history.size() - 1; // last jet
228+
# while (i >= 0) {
229+
# if (_history[i].max_dij_so_far <= dcut) {break;}
230+
# i--;
231+
# }
232+
# int stop_point = i + 1;
233+
# // relation between stop_point, njets assumes one extra jet disappears
234+
# // at each clustering.
235+
# int njets = 2*_initial_n - stop_point;
236+
# return njets;
237+
238+
end

src/GenericAlgo.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22
# switch based on the strategy, or based on the event density
33
# if the "Best" strategy is to be employed
44

5-
function jet_reconstruct(particles; p = -1, R = 1.0, recombine = +, strategy = JetRecoStrategy.Best)
5+
function jet_reconstruct(particles; p = -1, R = 1.0, recombine = +, strategy = RecoStrategy.Best)
66
# Either map to the fixed algorithm corresponding to the strategy
77
# or to an optimal choice based on the density of initial particles
88

9-
if strategy == JetRecoStrategy.Best
9+
if strategy == RecoStrategy.Best
1010
# The breakpoint of ~90 is determined empirically on e+e- -> H and 0.5TeV pp -> 5GeV jets
1111
algorithm = length(particles) > 80 ? tiled_jet_reconstruct : plain_jet_reconstruct
12-
elseif strategy == JetRecoStrategy.N2Plain
12+
elseif strategy == RecoStrategy.N2Plain
1313
algorithm = plain_jet_reconstruct
14-
elseif strategy == JetRecoStrategy.N2Tiled
14+
elseif strategy == RecoStrategy.N2Tiled
1515
algorithm = tiled_jet_reconstruct
1616
else
1717
throw(ErrorException("Invalid strategy: $(strategy)"))

src/JetRecoStrategies.jl

Lines changed: 0 additions & 14 deletions
This file was deleted.

src/JetReconstruction.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ energy(p::LorentzVectorCyl) = LorentzVectorHEP.energy(p)
2626
include("Pseudojet.jl")
2727
export PseudoJet
2828

29+
# Jet reconstruction strategies and algorithms (enums!)
30+
include("AlgorithmStrategyEnums.jl")
31+
export RecoStrategy, JetAlgorithm
32+
2933
# ClusterSequence type
3034
include("ClusterSequence.jl")
31-
export ClusterSequence, inclusive_jets
32-
33-
# Jet reconstruction strategies
34-
include("JetRecoStrategies.jl")
35-
export JetRecoStrategy
35+
export ClusterSequence, inclusive_jets, exclusive_jets
3636

3737
## N2Plain algorithm
3838
# Algorithmic part for simple sequential implementation

src/PlainAlgo.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ function _plain_jet_reconstruct(;particles::Vector{PseudoJet}, p = -1, R = 1.0,
134134
# Current implementation mutates the particles vector, so need to copy it
135135
# for the cluster sequence (there is too much copying happening, so this
136136
# needs to be rethought and reoptimised)
137-
clusterseq = ClusterSequence(particles, history, Qtot)
137+
clusterseq = ClusterSequence(p, RecoStrategy.N2Plain, particles, history, Qtot)
138138

139139
# Initialize nearest neighbours
140140
@simd for i in 1:N
@@ -155,7 +155,7 @@ function _plain_jet_reconstruct(;particles::Vector{PseudoJet}, p = -1, R = 1.0,
155155
#@debug "Beginning iteration $iteration"
156156
# Findmin and add back renormalisation to distance
157157
dij_min, i = fast_findmin(nndij, N)
158-
dij_min *= R2
158+
@fastmath dij_min /= R2
159159
j::Int = nn[i]
160160
#@debug "Closest compact jets are $i ($(clusterseq_index[i])) and $j ($(clusterseq_index[j]))"
161161

0 commit comments

Comments
 (0)