Skip to content

Commit 6f0c43c

Browse files
Make jet structs immutable and improve jet struct methods (#146)
This PR makes the jet structures used in the package immutable. This is relatively straight forward, but some care is required with the _cluster_hist_index, which is the link from the jet to the correct point in the reconstruction history. This now has to be set correctly at the time of object construction. This is addressed in a few places: For constructing an initial vector of jets, it's necessary to pre-set the cluster history to 1...N, so the initial jets are correctly lined up This is done in the reconstruction entry points automatically if conversion to the internal EDM is happening When reading from HepMC3 files to a FourMomentum struct, this is done During the reconstruction it is easy to calculate the correct point in the history. This requires the recombine function to be restructured to have the signature addjets(jet1, jet2; cluster_hist_index=index), which is not the same a simple +. However, it's not terrible and this is added to the documentation. Support is added for known schemes, currently massless Pt and Pt^2. The bells-and-whistles reconstruction example, instrumented-jetreco.jl, can now take an --recombine option to use non-standard recombination. Note that all of the jet struct constructors have been reworked to provide a more consistent interface and cluster_hist_index is always a named parameter. This PR also restructures the accessor/auxiliary methods for all the jet types, making these as generic as possible. To this end a new source file is added, CommonJetStructs.jl that factorises all of the generic type definitions and methods. Constructing jet types from LorentzVectors and LorentzVectorHEPs is made more consistent. Core reconstruction times are slightly improved with these static structs, especially on x86_64. For substructure functions, it was necessary to redo the recluster() method, to make copies of the relevant jets, with updated cluster indexes for the reclustering. This is actually a bug fix as the old way was updating the cluster indexes for the reclustering, which would break the reconstruction history for the original reconstruction. Docstrings were improved - adding missing ones and simplifying a bit others.
1 parent 1ce3921 commit 6f0c43c

29 files changed

+1092
-715
lines changed

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ algorithm and generalised $`k_\text{T}`$ for $`e^+e^-`$.
2727
The simplest interface is to call:
2828

2929
```julia
30-
cs = jet_reconstruct(particles::AbstractVector{T}; algorithm = JetAlgorithm.AntiKt, R = 1.0, [p = -1,] [recombine = +,] [strategy = RecoStrategy.Best])
30+
cs = jet_reconstruct(particles::AbstractVector{T}; algorithm = JetAlgorithm.AntiKt, R = 1.0, [p = -1,] [strategy = RecoStrategy.Best])
3131
```
3232

3333
- `particles` - a one dimensional array (vector) of input particles for the clustering
@@ -42,7 +42,6 @@ cs = jet_reconstruct(particles::AbstractVector{T}; algorithm = JetAlgorithm.Anti
4242
- `JetAlgorithm.Durham` the $e^+e-$ $k_\text{T}$ algorithm, also known as the Durham algorithm
4343
- `JetAlgorithm.EEKt` the $e^+e-$ generalised $k_\text{T}$ algorithm
4444
- `R` - the cone size parameter; no particles more geometrically distance than `R` will be merged (default 1.0; note this parameter is ignored for the Durham algorithm)
45-
- `recombine` - the function used to merge two pseudojets (default is a simple 4-vector addition of $`(E, \mathbf{p})`$)
4645
- `strategy` - the algorithm strategy to adopt, as described below (default `RecoStrategy.Best`)
4746

4847
The object returned is a `ClusterSequence`, which internally tracks all merge steps.
@@ -90,7 +89,7 @@ Another option, if one wishes to use a specific strategy, is to call that strate
9089

9190
```julia
9291
# For N2Plain strategy called directly
93-
plain_jet_reconstruct(particles::AbstractVector{T}; algorithm = JetAlgorithm.AntiKt, R = 1.0, recombine = +)
92+
plain_jet_reconstruct(particles::AbstractVector{T}; algorithm = JetAlgorithm.AntiKt, R = 1.0)
9493
```
9594

9695
Note that there is no `strategy` option in these interfaces.

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ makedocs(sitename = "JetReconstruction.jl",
1818
"Substructure" => "substructure.md",
1919
"Jet Helpers" => "helpers.md",
2020
"EDM4hep" => "EDM4hep.md",
21+
"Recombination Schemes" => "recombination.md",
2122
"Visualisation" => "visualisation.md",
2223
"Contributing" => "contributing.md",
2324
"Reference Docs" => Any["Public API" => "lib/public.md",

docs/src/index.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ or with some of the optional arguments,
2727

2828
```julia
2929
jet_reconstruct(particles; algorithm = JetAlgorithm.GenKt, R = 0.4,
30-
p = 0.5, recombine = +, strategy = RecoStrategy.Best)
30+
p = 0.5, recombine = addjets, strategy = RecoStrategy.Best)
3131
```
3232

3333
Where `particles` is a collection of 4-vector objects (see [Input Particle
@@ -40,6 +40,8 @@ algorithm (`GenKt`, `EEKt`) and `p` are needed.
4040
The `R` value determines the cone size; in the case of the Durham algorithm the
4141
`R` value is ignored.
4242

43+
For a discussion of the `recombine` function, see [Jet Recombination](@ref).
44+
4345
The object returned is a [`ClusterSequence`](@ref), which internally tracks all
4446
merge steps and is used for [Inclusive and Exclusive Selections](@ref).
4547

docs/src/recombination.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# Jet Recombination
2+
3+
When two jets are merged different strategies can be adopted to produce the merged jet.
4+
5+
There are two functions to support this, which can be set by the user and passed
6+
as a parameters to the reconstruction algorithms. One function gives the
7+
necessary *preprocessing* for input particles, e.g., setting particles to be
8+
massless. The other controls the actual *recombination* of two particles into a
9+
merged jet.
10+
11+
These functions are passed as the `preprocess` and `recombine` parameters to the
12+
reconstruction interfaces.
13+
14+
## Default - Four Vector Addition
15+
16+
The default for jet merging is simply four momentum addition, that is:
17+
18+
``
19+
(\mathbf{p}_m, E_m) = (\mathbf{p_1} + \mathbf{p_2}, E_1 + E_2)
20+
``
21+
22+
This is defined as the [`addjets`](@ref) function in the package, which also
23+
serves as an example of how the recombination functions are written.
24+
25+
In this case, no preprocessing of particles is required and the default value of
26+
`preprocess = nothing` signals this.
27+
28+
### Different Recombination Schemes
29+
30+
Two additional recombination schemes are directly supported, the ``p_T`` and
31+
``p_T^2`` schemes. In these schemes the recombined jet is created to be
32+
*massless*, i.e., the mass is set to the 3-momentum. The transverse momentum is
33+
the sum of the two parent jets and the rapidity (``y``) and phi (``\phi``)
34+
values are weighted averages, by ``p_T`` or ``p_T^2``, of the parent jets.
35+
36+
- `recombine =` [`addjets_ptscheme`](@ref)
37+
- `recombine =` [`addjets_pt2scheme`](@ref)
38+
39+
In this case the input particles must be rescaled to be massless, setting the
40+
energy equal to the (three) momentum sum.
41+
42+
- `preprocess =` [`preprocess_ptscheme`](@ref)
43+
- `preprocess =` [`preprocess_pt2scheme`](@ref)
44+
45+
(In fact `preprocess_pt2scheme` is just an alias for `preprocess_ptscheme` as
46+
the rescaling is identical.)
47+
48+
### Named Recombination Schemes
49+
50+
To simplify the usage of different recombination schemes supported directly,
51+
there is a defined enum (scoped, using `EnumX`) for each one:
52+
`RecombinationScheme.SCHEME`.
53+
54+
This enum is then used with the `RecombinationMethods` dictionary to
55+
obtain a named tuple in which `recombine` and `preprocess` are set, which can
56+
then be splatted into the [`jet_reconstruct`](@ref) interface:
57+
58+
```julia
59+
myscheme = RecombinationMethods[RecombinationScheme.PtScheme]
60+
jet_reconstruct(event; R = distance, p = p, algorithm = algorithm,
61+
strategy = strategy, myscheme...)
62+
```
63+
64+
The supported values in the enum are:
65+
66+
| Scheme | Implements |
67+
|---|---|
68+
| `EScheme` | Default 4-momentum addition |
69+
| `PtScheme` | Massless weighted average of momentum |
70+
| `Pt2Scheme` | Massless weighted average of momentum squared |
71+
72+
(Should other schemes prove to be particularly desired they can be implemented
73+
on request.)
74+
75+
## User Defined Recombination
76+
77+
### Preprocessing
78+
79+
The user must supply, if needed, a preprocessing function, which accepts an
80+
input particle and returns the rescaled particle. This function must accept a
81+
named argument `cluster_hist_index` to pass to the constructor of the resulting
82+
particle.
83+
84+
```julia
85+
user_preprocess(jet::T; cluster_hist_index) -> T
86+
```
87+
88+
An example of a preprocessing function is [`preprocess_ptscheme`](@ref).
89+
90+
### Recombination
91+
92+
If a different merging scheme is desired then a method must be defined
93+
that implements the following interface:
94+
95+
```julia
96+
user_recombine(jet1::T, jet2::T; cluster_hist_index::Int) where {T <: FourMomentum} -> T
97+
```
98+
99+
i.e., three arguments are needed, the two parent jets and the named argument
100+
`cluster_hist_index`, which is needed to identify the jet in the reconstruction
101+
sequence.
102+
103+
It is recommended to use the constructor signature for the output jet of:
104+
105+
```julia
106+
T(px, py, pz, E; cluster_hist_index = cluster_hist_index)
107+
```
108+
109+
Where `px`, `py`, `pz` and `E` have been calculated from the inputs `jet1` and
110+
`jet2` as desired.
111+
112+
However, if working in ``(p_T, y, \phi, m)`` space, use the alternative constructor
113+
with named parameters:
114+
115+
```julia
116+
T(;pt=pt, rap=rap, phi=phi, m=m, cluster_hist_index=cluster_hist_index)
117+
```
118+
119+
(Note that there is a default of `m=0.0`, which is used for massless
120+
recombination.)
121+
122+
The user function should not modify the `cluster_hist_index`, but must pass in
123+
to the new jet's constructor to ensure that the resulting reconstruction
124+
[`ClusterSequence`](@ref) is valid. The recombination functions defined in the
125+
package serve as examples: [`addjets_ptscheme`](@ref).
126+
127+
### Using an Custom Recombination Method
128+
129+
To use a non-default recombination method, simply pass the recombination method
130+
to the [`jet_reconstruct`](@ref) entry point as the `recombine` parameter and
131+
the preprocessing method as `preprocess`.
132+
133+
A very convenient way to do this is to bind these functions into a named tuple
134+
and splat the tuple into the arguments for the reconstruction.

examples/benchmark.sh

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#! /bin/sh
22
#
33
# Quick and dirty set of benchmarks for the most important cases
4+
trials=${1:-16}
5+
46
echo "pp 14TeV Tiled"
5-
julia --project instrumented-jetreco.jl --algorithm=AntiKt -R 0.4 ../test/data/events.pp13TeV.hepmc3.gz -S N2Tiled -m 16
7+
julia --project instrumented-jetreco.jl --algorithm=AntiKt -R 0.4 ../test/data/events.pp13TeV.hepmc3.gz -S N2Tiled -m $trials
68

79
echo "pp 14 TeV Plain"
8-
julia --project instrumented-jetreco.jl --algorithm=AntiKt -R 0.4 ../test/data/events.pp13TeV.hepmc3.gz -S N2Plain -m 16
10+
julia --project instrumented-jetreco.jl --algorithm=AntiKt -R 0.4 ../test/data/events.pp13TeV.hepmc3.gz -S N2Plain -m $trials
911

1012
echo "ee H Durham"
11-
julia --project instrumented-jetreco.jl --algorithm=Durham ../test/data/events.eeH.hepmc3.gz -m 16
13+
julia --project instrumented-jetreco.jl --algorithm=Durham ../test/data/events.eeH.hepmc3.gz -m $trials

examples/instrumented-jetreco.jl

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@ flamegraph which is saved to the `profile/profile_subdir` directory.
3333
"""
3434
function profile_code(events::Vector{Vector{T}}, profile, nsamples; R = 0.4, p = -1,
3535
algorithm::JetAlgorithm.Algorithm = JetAlgorithm.AntiKt,
36-
strategy = RecoStrategy.N2Tiled) where {T <:
37-
JetReconstruction.FourMomentum}
36+
strategy = RecoStrategy.N2Tiled,
37+
recombine = RecombinationMethods[RecombinationScheme.EScheme]) where {T <:
38+
JetReconstruction.FourMomentum}
3839
Profile.init(n = 5 * 10^6, delay = 0.00001)
3940
function profile_events(events)
4041
for evt in events
41-
jet_reconstruct(evt, R = R, p = p, algorithm = algorithm,
42-
strategy = strategy)
42+
jet_reconstruct(evt; R = R, p = p, algorithm = algorithm,
43+
strategy = strategy, recombine...)
4344
end
4445
end
4546
# Do a warm up run first to avoid JIT compilation costs
@@ -88,12 +89,14 @@ function allocation_stats(events::Vector{Vector{T}}; distance::Real = 0.4,
8889
p::Union{Real, Nothing} = nothing,
8990
algorithm::Union{JetAlgorithm.Algorithm, Nothing} = nothing,
9091
strategy::RecoStrategy.Strategy,
92+
recombine = RecombinationMethods[RecombinationScheme.EScheme],
9193
ptmin::Real = 5.0) where {T <: JetReconstruction.FourMomentum}
9294
println("Memory allocation statistics:")
9395
@timev for event in events
94-
_ = inclusive_jets(jet_reconstruct(event, R = distance, p = p,
96+
_ = inclusive_jets(jet_reconstruct(event; R = distance, p = p,
9597
algorithm = algorithm,
96-
strategy = strategy), ptmin = ptmin)
98+
strategy = strategy, recombine...),
99+
ptmin = ptmin)
97100
end
98101
nothing
99102
end
@@ -125,10 +128,11 @@ function benchmark_jet_reco(events::Vector{Vector{T}};
125128
distance::Real = 0.4,
126129
algorithm::Union{JetAlgorithm.Algorithm, Nothing} = nothing,
127130
p::Union{Real, Nothing} = nothing,
131+
strategy::RecoStrategy.Strategy,
132+
recombine = RecombinationMethods[RecombinationScheme.EScheme],
128133
ptmin::Real = 5.0,
129134
dcut = nothing,
130135
njets = nothing,
131-
strategy::RecoStrategy.Strategy,
132136
nsamples::Integer = 1,
133137
gcoff::Bool = false,
134138
dump::Union{String, Nothing} = nothing,
@@ -155,8 +159,8 @@ function benchmark_jet_reco(events::Vector{Vector{T}};
155159
gcoff && GC.enable(false)
156160
t_start = time_ns()
157161
for (ievt, event) in enumerate(events)
158-
cs = jet_reconstruct(event, R = distance, p = p, algorithm = algorithm,
159-
strategy = strategy)
162+
cs = jet_reconstruct(event; R = distance, p = p, algorithm = algorithm,
163+
strategy = strategy, recombine...)
160164
if !isnothing(njets)
161165
finaljets = exclusive_jets(cs; njets = njets)
162166
elseif !isnothing(dcut)
@@ -273,6 +277,11 @@ function parse_command_line(args)
273277
arg_type = RecoStrategy.Strategy
274278
default = RecoStrategy.Best
275279

280+
"--recombine"
281+
help = """Recombination scheme to use for jet reconstruction: $(join(JetReconstruction.AllRecombinationSchemes, ", "))"""
282+
arg_type = RecombinationScheme.Recombine
283+
default = RecombinationScheme.EScheme
284+
276285
"--nsamples", "-m"
277286
help = "Number of measurement points to acquire."
278287
arg_type = Int
@@ -344,15 +353,19 @@ function main()
344353
if args[:alloc]
345354
allocation_stats(events; distance = args[:distance],
346355
p = args[:power], algorithm = args[:algorithm],
347-
strategy = args[:strategy], ptmin = args[:ptmin])
356+
strategy = args[:strategy],
357+
recombine = JetReconstruction.RecombinationMethods[args[:recombine]],
358+
ptmin = args[:ptmin])
348359
elseif !isnothing(args[:profile])
349360
profile_code(events, args[:profile], args[:nsamples];
350361
R = args[:distance], p = args[:power],
351-
algorithm = args[:algorithm], strategy = args[:strategy])
362+
algorithm = args[:algorithm], strategy = args[:strategy],
363+
recombine = JetReconstruction.RecombinationMethods[args[:recombine]])
352364
else
353365
benchmark_jet_reco(events, distance = args[:distance], algorithm = args[:algorithm],
354366
p = args[:power],
355367
strategy = args[:strategy],
368+
recombine = JetReconstruction.RecombinationMethods[args[:recombine]],
356369
ptmin = args[:ptmin], dcut = args[:exclusive_dcut],
357370
njets = args[:exclusive_njets],
358371
nsamples = args[:nsamples], gcoff = args[:gcoff],

examples/parse-options.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,11 @@ function ArgParse.parse_item(E::Type{RecoStrategy.Strategy}, x::AbstractString)
2424
end
2525
p
2626
end
27+
28+
function ArgParse.parse_item(E::Type{RecombinationScheme.Recombine}, x::AbstractString)
29+
p = do_enum_parse(E, x)
30+
if p === nothing
31+
throw(ErrorException("Invalid value for recombination scheme: $(x)"))
32+
end
33+
p
34+
end

examples/visualisation/visualise-jets.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,9 @@ function main()
6666
maxevents = args[:event],
6767
skipevents = args[:event])
6868

69-
(p, algorithm) = JetReconstruction.get_algorithm_power_consistency(p = args[:power],
70-
algorithm = args[:algorithm])
69+
(p,
70+
algorithm) = JetReconstruction.get_algorithm_power_consistency(p = args[:power],
71+
algorithm = args[:algorithm])
7172
cs = jet_reconstruct(events[1], R = args[:distance], p = p, algorithm = algorithm,
7273
strategy = args[:strategy])
7374

ext/JetVisualisation.jl

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -144,21 +144,22 @@ function JetReconstruction.jetsplot(cs::ClusterSequence,
144144
end
145145

146146
set_theme!(jetreco_theme)
147-
fig, ax, plt_obj = Module.meshscatter(jet_plot_points;
148-
markersize = jet_plot_marker_size,
149-
marker = jet_plot_marker,
150-
colormap = colormap,
151-
color = jet_plot_colours,
152-
colorrange = (1, 256),
153-
figure = (size = (700, 600),),
154-
axis = (type = Axis3, perspectiveness = 0.5,
155-
azimuth = 2.7,
156-
elevation = 0.5,
157-
xlabel = L"\phi", ylabel = L"y",
158-
zlabel = L"p_T",
159-
limits = (0, 2π, min_rap - 0.5,
160-
max_rap + 0.5, 0, max_pt + 10)),
161-
shading = NoShading)
147+
fig, ax,
148+
plt_obj = Module.meshscatter(jet_plot_points;
149+
markersize = jet_plot_marker_size,
150+
marker = jet_plot_marker,
151+
colormap = colormap,
152+
color = jet_plot_colours,
153+
colorrange = (1, 256),
154+
figure = (size = (700, 600),),
155+
axis = (type = Axis3, perspectiveness = 0.5,
156+
azimuth = 2.7,
157+
elevation = 0.5,
158+
xlabel = L"\phi", ylabel = L"y",
159+
zlabel = L"p_T",
160+
limits = (0, 2π, min_rap - 0.5,
161+
max_rap + 0.5, 0, max_pt + 10)),
162+
shading = NoShading)
162163
fig, ax, plt_obj
163164
end
164165

src/AlgorithmStrategyEnums.jl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,26 @@ Check if the algorithm is a e+e- reconstruction algorithm.
150150
function is_ee(algorithm::JetAlgorithm.Algorithm)
151151
return algorithm in [JetAlgorithm.EEKt, JetAlgorithm.Durham]
152152
end
153+
154+
"""
155+
enum RecombinationScheme
156+
157+
An EnumX scoped enumeration representing different recombination schemes that
158+
are supported directly in the package.
159+
160+
These schemes map to both a `recombine` and a `preprocess` function, which are
161+
used in the main reconstruction algorithm.
162+
"""
163+
@enumx T=Recombine RecombinationScheme EScheme PtScheme Pt2Scheme
164+
const AllRecombinationSchemes = [String(Symbol(x))
165+
for x in instances(RecombinationScheme.Recombine)]
166+
167+
# Note it's a bit fragile to have the dictionary and the enum built
168+
# separately, but it is manageable. There is a test in the CI that
169+
# checks that all the enums are defined in the dictionary.
170+
const RecombinationMethods = Dict(RecombinationScheme.EScheme => (recombine = addjets_escheme,
171+
preprocess = nothing),
172+
RecombinationScheme.PtScheme => (recombine = addjets_ptscheme,
173+
preprocess = preprocess_ptscheme),
174+
RecombinationScheme.Pt2Scheme => (recombine = addjets_pt2scheme,
175+
preprocess = preprocess_pt2scheme))

0 commit comments

Comments
 (0)