Skip to content

Commit c40fa56

Browse files
Implementing modules for Lund Jet Plane generation (#184)
* Implementing modules for Lund Jet Plane generation * Refactor Lund-plane utilities Moved `generate_average_lund_image` function to `examples/lundplane/lund-plane-visualisation.jl` Included reference to original paper Removed redundant comments * Fixed the issue with tests of LundPlane * Small fixes for Lund plane Rephrase a few things in the documentation. Use LaTeXStrings for nicer axis labels on example. Add a max_allowable_R for the package (following Fastjet this is set to 1000) Make use of the delta_phi() function in deltaR() and deltar(). * Minor formatting --------- Co-authored-by: Graeme A Stewart <[email protected]>
1 parent 606a7dd commit c40fa56

File tree

14 files changed

+401
-7
lines changed

14 files changed

+401
-7
lines changed

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ makedocs(sitename = "JetReconstruction.jl",
1616
"Particle Inputs" => "particles.md",
1717
"Reconstruction Strategies" => "strategy.md",
1818
"Substructure" => "substructure.md",
19+
"Lund Jet Plane" => "lundplane.md",
1920
"Jet Helpers" => "helpers.md",
2021
"EDM4hep" => "EDM4hep.md",
2122
"Recombination Schemes" => "recombination.md",

docs/src/lundplane.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Lund Jet Plane
2+
3+
The Lund jet plane ([arXiv:1807.04758](https://arxiv.org/abs/1807.04758)) is a simple and intuitive way to visualize how a jet breaks up into smaller components. Each point in the plane represents a branching in the jet, helping understand how energy and angles are distributed within.
4+
5+
---
6+
7+
### Decluster
8+
9+
```julia
10+
decluster(jet::T, clusterseq::ClusterSequence{T}) where {T <: FourMomentum} -> Tuple{PseudoJet}
11+
```
12+
13+
Recursively declusters a jet into two parent subjets using a provided clustering sequence. The subjets are ordered by decreasing transverse momentum squared (`pt²`).
14+
15+
```julia
16+
j1, j2 = decluster(jet, clusterseq)
17+
```
18+
19+
- If no parents are found, the function returns `nothing` for one or both subjets.
20+
21+
Useful for building recursive jet trees and jet grooming algorithms.
22+
23+
---
24+
25+
### Generate Lund Emissions
26+
27+
```julia
28+
generate_lund_emissions(jet::PseudoJet, cs::ClusterSequence{PseudoJet}) -> Vector{NamedTuple}
29+
```
30+
31+
Constructs the Lund plane emissions of a jet. The result is a list of declustering steps, each represented by a named tuple of physics observables.
32+
33+
```julia
34+
lund_points = generate_lund_emissions(jet, cluster_seq)
35+
```
36+
37+
Each named tuple includes:
38+
39+
- `h_pt`: transverse momentum of the harder branch
40+
- `s_pt`: transverse momentum of the softer branch
41+
- `z`: momentum fraction
42+
- `delta`: angular distance ΔR
43+
- `kt`: transverse momentum of the softer branch relative to the harder
44+
- `psi`: azimuthal angle between the two splittings
45+
- `kappa`: z × ΔR
46+
47+
These values are useful for visualization and physics analysis of jet splittings.
48+
49+
---
50+
51+
### Visualisation of Lund Jet Plane
52+
53+
`examples/lundplane/lund-plane-visualisation.jl` provides an example for generating and plotting the average lund image.
54+
55+
```julia
56+
generate_average_lund_image(njets::Int, delta_array::Vector{Vector{Real}}, kt_array::Vector{Vector{Real}}; xrange::Tuple{Real}, yrange::Tuple{Real}, bins::Int) -> (xgrid, ygrid, avg_image)
57+
```
58+
59+
Computes the average Lund image over a set of jets, by binning (log(1/ΔR), log(kt)) values from each jet into a 2D histogram and averaging the histograms per jet.
60+
61+
```julia
62+
x, y, avg_image = generate_average_lund_image(njets, delta_array, kt_array; bins=25)
63+
```
64+
65+
- `delta_array` and `kt_array` should be arrays of arrays: one per jet.
66+
- The output is a tuple of x and y bin edges, and a 2D image array.
67+
- This is useful for creating heatmaps to compare jet substructure distributions.
68+
69+
---
70+
71+
For further examples and advanced use cases, see the documentation in the `examples/lundplane/` directory

examples/lundplane/Project.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[deps]
2+
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
3+
JetReconstruction = "44e8cb2c-dfab-4825-9c70-d4808a591196"
4+
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
5+
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
6+
7+
[sources]
8+
JetReconstruction = {path = "../.."}

examples/lundplane/README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Examples for generating Lund Jet Plane
2+
3+
## `lund-jet-generation.jl`
4+
5+
This script:
6+
7+
- Loads events from a sample data file
8+
- Clusters final state particles using the anti-kt algorithm
9+
- Sorts jets by transverse momentum squared
10+
- Applies `generate_lund_emissions` on each jet to calculate all the lund plane emissions
11+
- Prints the number of lund plane emissions for each jet
12+
13+
### How to Run
14+
15+
```julia
16+
julia --project lund-jet-generation.jl
17+
```
18+
19+
### Customization
20+
21+
- **Event selection**: Change `event_no = 1` to select a different event.
22+
- **Input data**: Replace the default path in `input_file` with your custom dataset.
23+
24+
---
25+
26+
## `lund-plane-visualisation.jl`
27+
28+
This script:
29+
30+
- Runs over all events in the dataset
31+
- Extracts Lund-plane variables for each jet (`kt`, `ΔR`)
32+
- Computes the average Lund plane using `generate_average_lund_image`
33+
- Visualizes it using `CairoMakie`
34+
- Saves the heatmap as `lund-gen.png`
35+
36+
### How to Run
37+
38+
```julia
39+
julia --project lund-plane-visualisation.jl
40+
```
41+
42+
### Output
43+
44+
- A PNG image `lund-gen.png` will be saved.
45+
46+
### Customization
47+
48+
- **Input data**: Replace the default path in `input_file` with your custom dataset.
49+
- **Jet algorithm parameters**: Modify `R = 1.0`, `ptmin = 10.0`, or the clustering algorithm.
50+
- **Binning**: Adjust `bins` in `generate_average_lund_image` to control resolution.
51+
- **Axis ranges**: Tune `xrange` and `yrange` to zoom in/out of particular regions.
52+
- **Visuals**: Customize colormap or labels in the `heatmap!` function.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using JetReconstruction
2+
3+
input_file = joinpath(dirname(pathof(JetReconstruction)),
4+
"..", "test", "data", "events.pp13TeV.hepmc3.zst")
5+
6+
events = read_final_state_particles(input_file)
7+
8+
# Event to pick
9+
event_no = 1
10+
11+
cluster_seq = jet_reconstruct(events[event_no]; algorithm = JetAlgorithm.AntiKt, R = 1.0)
12+
jets = sort!(inclusive_jets(cluster_seq, PseudoJet, ptmin = 10.0),
13+
by = JetReconstruction.pt2, rev = true)
14+
15+
@info "Generating Primary Lund Emissions for $(length(jets)) jets for Event $(event_no):"
16+
for (ijet, jet) in enumerate(jets)
17+
lundvars = generate_lund_emissions(jet, cluster_seq)
18+
println("- Jet $(ijet) has $(length(lundvars)) emissions in lund plane")
19+
end
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
using JetReconstruction
2+
using CairoMakie
3+
using StatsBase
4+
using LaTeXStrings
5+
6+
"""
7+
generate_average_lund_image(njets::Int, delta_array::Vector{Vector{Real}},
8+
kt_array::Vector{Vector{Real}}; xrange::Tuple{Real},
9+
yrange::Tuple{Real}, bins::Int) -> (xgrid, ygrid, avg_image)
10+
11+
Computes an average Lund image from a set of jets by binning the (log(1/ΔR), log(kt))
12+
coordinates into a fixed-size 2D histogram. Each jet's histogram is normalized and
13+
then averaged across all jets.
14+
15+
Arguments:
16+
- `njets`: Number of jets
17+
- `delta_array`: Vector of vectors, where each inner vector contains ΔR values for a jet
18+
- `kt_array`: Vector of vectors, where each inner vector contains kt values for a jet
19+
- `xrange`: Tuple defining the x-axis (log(1/ΔR)) range
20+
- `yrange`: Tuple defining the y-axis (log(kt)) range
21+
- `bins`: Number of bins along each axis
22+
23+
Returns:
24+
- A tuple `(xgrid, ygrid, avg_image)` where `xgrid` and `ygrid` are coordinate axes labels and
25+
`avg_image` is the averaged 2D histogram as a matrix
26+
"""
27+
function generate_average_lund_image(njets::Int, delta_array::Vector{Vector{Real}},
28+
kt_array::Vector{Vector{Real}}; xrange = (0.0, 4.0),
29+
yrange = (-5.0, 7.0), bins = 25)
30+
xmin, xmax = xrange
31+
ymin, ymax = yrange
32+
33+
x_width = (xmax - xmin) / bins
34+
y_width = (ymax - ymin) / bins
35+
36+
total_res = []
37+
38+
for i in 1:njets
39+
Xind = ceil.((delta_array[i] .- xmin) ./ x_width)
40+
Yind = ceil.((kt_array[i] .- ymin) ./ y_width)
41+
42+
res = zeros(Float32, bins, bins)
43+
L1norm = 0.0
44+
45+
for i in 1:length(Xind)
46+
x = Int(Xind[i])
47+
y = Int(Yind[i])
48+
if (maximum([x, y]) < bins && minimum([x, y]) >= 1)
49+
res[x, y] += 1
50+
L1norm += 1
51+
end
52+
end
53+
54+
if L1norm > 0
55+
res[1, :] .= res[1, :] ./ L1norm
56+
push!(total_res, res)
57+
end
58+
end
59+
60+
avg_res = mean(total_res, dims = 1)[1]
61+
x = range(xmin, xmax; length = bins)
62+
y = range(ymin, ymax; length = bins)
63+
64+
return (x, y, avg_res)
65+
end
66+
67+
input_file = joinpath(dirname(pathof(JetReconstruction)),
68+
"..", "test", "data", "events.pp13TeV.hepmc3.zst")
69+
events = read_final_state_particles(input_file)
70+
71+
N = length(events)
72+
lundX = Vector{Vector{Real}}()
73+
lundY = Vector{Vector{Real}}()
74+
75+
lund_kt(p) = p.kt
76+
lund_delta(p) = p.delta
77+
78+
# Event to pick
79+
for event_no in 1:N
80+
cluster_seq = jet_reconstruct(events[event_no]; algorithm = JetAlgorithm.AntiKt,
81+
R = 1.0)
82+
jets = sort!(inclusive_jets(cluster_seq, PseudoJet; ptmin = 10.0),
83+
by = JetReconstruction.pt2, rev = true)
84+
85+
@info "Generating Primary Lund Emissions for $(length(jets)) jets for Event $(event_no):"
86+
for (ijet, jet) in enumerate(jets)
87+
lundvars = generate_lund_emissions(jet, cluster_seq)
88+
println("- Jet $(ijet) has $(length(lundvars)) emissions in lund plane")
89+
kt = lund_kt.(lundvars)
90+
Δ = lund_delta.(lundvars)
91+
92+
push!(lundX, -log.(Δ))
93+
push!(lundY, log.(kt))
94+
end
95+
end
96+
97+
njets = length(lundX)
98+
x, y, avg_res = generate_average_lund_image(njets, lundX, lundY)
99+
100+
fig = Figure()
101+
ax = Axis(fig[1, 1], xlabel = L"\ln(R/\Delta)", ylabel = L"\ln(k_T/\mathrm{GeV})",
102+
title = "Average Lund Image")
103+
hm = heatmap!(ax, x, y, avg_res; colormap = :viridis, colorrange = extrema(avg_res))
104+
Colorbar(fig[1, 2], hm; label = "")
105+
106+
display(fig)
107+
108+
save("lund-gen.png", fig)

src/ClusterSequence.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,7 @@ given jet.
624624
An vector of indices representing the original constituents of the given jet.
625625
"""
626626
function constituent_indexes(jet::T, cs::ClusterSequence{T}) where {T <: FourMomentum}
627-
get_all_ancestors(cs.history[jet._cluster_hist_index].jetp_index, cs)
627+
get_all_ancestors(jet._cluster_hist_index, cs)
628628
end
629629

630630
"""

src/JetReconstruction.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ py(p::LorentzVectorCyl) = LorentzVectorHEP.py(p)
3636
pz(p::LorentzVectorCyl) = LorentzVectorHEP.pz(p)
3737
energy(p::LorentzVectorCyl) = LorentzVectorHEP.energy(p)
3838

39+
# Some useful constants/limits that are used internally
40+
const max_allowable_R = 1000.0
41+
3942
# Pseudojet and EEJet types
4043
include("CommonJetStructs.jl")
4144
include("PseudoJet.jl")
@@ -81,6 +84,9 @@ export jet_reconstruct
8184
include("Substructure.jl")
8285
export mass_drop, soft_drop, jet_filtering, jet_trimming
8386

87+
include("LundPlane.jl")
88+
export generate_lund_emissions
89+
8490
# Simple HepMC3 reader
8591
include("HepMC3.jl")
8692

src/JetUtils.jl

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ Function to calculate the distance in the y-ϕ plane between two jets `jet1` and
6767
"""
6868
function deltaR(jet1::T, jet2::T) where {T <: FourMomentum}
6969
δy = rapidity(jet1) - rapidity(jet2)
70-
δϕ = phi(jet1) - phi(jet2)
71-
δϕ = abs(δϕ) > π ? 2π - abs(δϕ) : δϕ
70+
δϕ = delta_phi(jet1, jet2)
7271

7372
return sqrt(δy^2 + δϕ^2)
7473
end
@@ -84,10 +83,25 @@ Function to calculate the distance in the η-ϕ plane between two jets `jet1` an
8483
"""
8584
function deltar(jet1::T, jet2::T) where {T <: FourMomentum}
8685
δη = eta(jet1) - eta(jet2)
86+
δϕ = delta_phi(jet1, jet2)
87+
88+
return sqrt(δη^2 + δϕ^2)
89+
end
90+
91+
"""
92+
delta_phi(jet1::T, jet2::T) where {T <: FourMomentum}
93+
94+
Computes the difference in azimuthal angle φ between two jets,
95+
wrapped into the range [-π, π].
96+
97+
# Returns
98+
- `δφ` as a Float64 in radians.
99+
"""
100+
function delta_phi(jet1::T, jet2::T) where {T <: FourMomentum}
87101
δϕ = phi(jet1) - phi(jet2)
88102
δϕ = abs(δϕ) > π ? 2π - abs(δϕ) : δϕ
89103

90-
return sqrt(δη^2 + δϕ^2)
104+
return δϕ
91105
end
92106

93107
"""

0 commit comments

Comments
 (0)