Skip to content

Commit a5a31c9

Browse files
Copilotmmikhasenko
andauthored
Add dalitzprojection recipe for 1D Dalitz plot projections (#83)
* Initial plan * Add dalitzprojection recipe and visualization tutorial Co-authored-by: mmikhasenko <[email protected]> * update visualization notebook * referenced visualization * update quad to the last argument * test integrals * added ; --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: mmikhasenko <[email protected]> Co-authored-by: Mikhail Mikhasenko <[email protected]>
1 parent d149d24 commit a5a31c9

File tree

7 files changed

+334
-37
lines changed

7 files changed

+334
-37
lines changed

.cspell/project.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ cosζj
1010
cosζk
1111
Dalitz
1212
dalitzplot
13+
dalitzprojection
1314
doublearg
1415
eachslice
16+
fillalpha
1517
findmin
1618
getindex
1719
getproperty
@@ -59,6 +61,7 @@ vcat
5961
Weisskopf
6062
wignerd
6163
xbins
64+
xguide
6265
xlab
6366
xlim
6467
Xlineshape

docs/src/10-energy-dependence.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ When building the model, these dependencies are specified as follows:
1717
```julia
1818
tbs = ThreeBodySystem(
1919
ThreeBodyMasses(1.0, 2.0, 3.0; m0=16.0),
20-
ThreeBodySpins(0, 0, 0; two_h0=0))
20+
ThreeBodySpins(0, 0, 0; two_h0=0));
2121

2222
L = 1 # orbital angular momentum for production
2323
l = 1 # orbital angular momentum for decay
@@ -32,7 +32,7 @@ chain1 = DecayChain(;
3232
Hij = VertexFunction( # decay form-factor
3333
RecouplingLS((2l, 0)), # (two_l, two_s)
3434
BlattWeisskopf{l}(1.5)), # {l}
35-
tbs)
35+
tbs);
3636
```
3737

3838
Many common models for the propagators and form factors are available in `HadronicLineshapes.jl`.
@@ -43,17 +43,17 @@ Alternatively, you can use lambda functions for simple cases, define any custom
4343
struct MyCoupling <: Recoupling
4444
# custom fields
4545
end
46-
amplitude(cs::MyCoupling, (two_λa, two_λb), (two_j, two_ja, two_jb)) # to defined
46+
amplitude(cs::MyCoupling, (two_λa, two_λb), (two_j, two_ja, two_jb)); # to defined
4747

4848
# Custom form factor
4949
struct MyFormFactor
5050
# custom fields
5151
end
52-
(ff::MyFormFactor)(m0², m1², m2²) # to defined
52+
(ff::MyFormFactor)(m0², m1², m2²); # to defined
5353

5454
# Custom propagator
5555
struct MyPropagator
5656
# custom fields
5757
end
58-
(prop::MyPropagator)(σk) # to defined
58+
(prop::MyPropagator)(σk); # to defined
5959
```

docs/src/30-tutorial.jl

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ theme(
2626
)
2727

2828
# decay Λb ⟶ Jψ p K
29-
constants = Dict("mJψ" => 3.09, "mp" => 0.938, "mK" => 0.49367, "mLb" => 5.62) # masses of the particles
29+
constants = Dict("mJψ" => 3.09, "mp" => 0.938, "mK" => 0.49367, "mLb" => 5.62); # masses of the particles
3030

3131
# `ThreeBodySystem` creates an immutable structure that describes the setup.
3232
# Two work with particles with non-integer spin, the doubled quantum numbers are stored.
@@ -36,13 +36,13 @@ ms = ThreeBodyMasses( # masses m1,m2,m3,m0
3636
constants["mp"],
3737
constants["mK"];
3838
m0 = constants["mLb"],
39-
)
39+
);
4040

4141
# create two-body system
42-
tbs = ThreeBodySystem(; ms, two_js = ThreeBodySpins(2, 1, 0; two_h0 = 1)) # twice spin
42+
tbs = ThreeBodySystem(; ms, two_js = ThreeBodySpins(2, 1, 0; two_h0 = 1)); # twice spin
4343

44-
Conserving = ThreeBodyParities('-', '+', '-'; P0 = '+')
45-
Violating = ThreeBodyParities('-', '+', '-'; P0 = '-')
44+
Conserving = ThreeBodyParities('-', '+', '-'; P0 = '+');
45+
Violating = ThreeBodyParities('-', '+', '-'; P0 = '-');
4646

4747
# - the invariant variables, `σs = [σ₁,σ₂,σ₃]`,
4848
# - helicities `two_λs = [λ₁,λ₂,λ₃,λ₀]`
@@ -60,7 +60,7 @@ struct BW
6060
m::Float64
6161
Γ::Float64
6262
end
63-
(bw::BW)(σ::Number) = 1 / (bw.m^2 - σ - 1im * bw.m * bw.Γ)
63+
(bw::BW)(σ::Number) = 1 / (bw.m^2 - σ - 1im * bw.m * bw.Γ);
6464

6565

6666
# chains-1, i.e. (2+3): Λs with the lowest ls, LS
@@ -70,21 +70,21 @@ end
7070
jp = jp"3/2+",
7171
Ps = Conserving,
7272
tbs = tbs,
73-
)
73+
);
7474
Λ1690 = DecayChainLS(
7575
k = 1,
7676
Xlineshape = BW(1.685, 0.050),
7777
jp = jp"1/2+",
7878
Ps = Conserving,
7979
tbs = tbs,
80-
)
80+
);
8181
Λ1810 = DecayChainLS(
8282
k = 1,
8383
Xlineshape = BW(1.80, 0.090),
8484
jp = jp"5/2+",
8585
Ps = Conserving,
8686
tbs = tbs,
87-
)
87+
);
8888
Λs = (Λ1520, Λ1690, Λ1810);
8989

9090
# chains-3, i.e. (1+2): Pentaquarks with the lowest ls, LS
@@ -94,21 +94,21 @@ Pc4312 = DecayChainLS(;
9494
jp = jp"1/2+",
9595
Ps = Conserving,
9696
tbs = tbs,
97-
)
97+
);
9898
Pc4440 = DecayChainLS(;
9999
k = 3,
100100
Xlineshape = BW(4.440, 0.010),
101101
jp = jp"1/2+",
102102
Ps = Conserving,
103103
tbs = tbs,
104-
)
104+
);
105105
Pc4457 = DecayChainLS(;
106106
k = 3,
107107
Xlineshape = BW(4.457, 0.020),
108108
jp = jp"3/2+",
109109
Ps = Conserving,
110110
tbs = tbs,
111-
)
111+
);
112112
Pcs = (Pc4312, Pc4440, Pc4457);
113113

114114
# ## Unpolarized intensity
@@ -121,17 +121,17 @@ const model = ThreeBodyDecay(
121121
[2, 2.1, 1.4im, 0.4, 0.3im, -0.8im],
122122
[Λ1520, Λ1690, Λ1810, Pc4312, Pc4440, Pc4457],
123123
),
124-
)
124+
);
125125

126126
# just a random point of the Dalitz Plot
127-
σs = randomPoint(tbs.ms)
128-
two_λs = randomPoint(tbs.two_js)
127+
σs = randomPoint(tbs.ms);
128+
two_λs = randomPoint(tbs.two_js);
129129

130130
# Model is build, one can compute unpolarized intensity with it
131-
@show amplitude(model, σs, two_λs)
131+
@show amplitude(model, σs, two_λs);
132132

133133
# gives a real number - probability
134-
@show unpolarized_intensity(model, σs)
134+
@show unpolarized_intensity(model, σs);
135135

136136
# ## Plotting API
137137
#
@@ -151,20 +151,12 @@ plot(
151151
# Visualize the matrix element as a function of kinematic variables:
152152

153153
plot(masses(model), σs -> abs2(amplitude(Pc4312, σs, (2, -1, 0, 1))))
154-
dalitzplot(
155-
masses(model),
156-
Base.Fix1(unpolarized_intensity, model);
157-
iσx = 1,
158-
iσy = 3,
159-
xlim = (2.0, 4.0),
160-
ylim = (20.0, 27.0),
161-
xbins = 100,
162-
ybins = 120,
163-
)
164154

165155
# Compute Dalitz plot projections numerically:
166156

167157
plot(4.2, 4.6) do e1
168158
I = Base.Fix1(unpolarized_intensity, model)
169159
e1 * quadgk(projection_integrand(I, masses(model), e1^2; k = 3), 0, 1)[1]
170160
end
161+
162+
# See more in the [Visualization Tutorial](32-visualization.md).

docs/src/32-visualization.jl

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
#md # # Visualization Tutorial
2+
#nb # # ThreeBodyDecays.jl Visualization Tutorial
3+
#jl # # ThreeBodyDecays.jl Visualization Tutorial
4+
5+
# This tutorial demonstrates how to visualize three-body decay models using
6+
# Dalitz plots and Dalitz plot projections. We'll use the same Λb ⟶ J/ψ p K
7+
# decay example from the main tutorial.
8+
9+
using ThreeBodyDecays
10+
using Plots
11+
using QuadGK
12+
#
13+
theme(
14+
:wong,
15+
frame = :box,
16+
lab = "",
17+
minorticks = true,
18+
guidefontvalign = :top,
19+
guidefonthalign = :right,
20+
xlim = (:auto, :auto),
21+
ylim = (:auto, :auto),
22+
grid = false,
23+
);
24+
25+
# ## Setting up Kinematics
26+
27+
# First, let's define the masses and spin of particles:
28+
29+
ms = ThreeBodyMasses(3.09, 0.938, 0.49367; m0 = 5.62);
30+
tbs = ThreeBodySystem(; ms, two_js = ThreeBodySpins(2, 1, 0; two_h0 = 1));
31+
32+
# ## Phase Space Boundaries
33+
34+
# You can also visualize the phase space boundaries:
35+
36+
plot(
37+
border31(ms),
38+
xlab = "σ₃ [GeV²]",
39+
ylab = "σ₁ [GeV²]",
40+
title = "Phase Space Boundary",
41+
aspect_ratio = 1,
42+
);
43+
44+
# The `dalitzplot` and `dalitzprojection` recipes provide a flexible way to
45+
# visualize three-body decay models. The key parameters are:
46+
#
47+
# **For `dalitzplot`:**
48+
# - `iσx`, `iσy`: Choose which invariants to plot (1, 2, or 3)
49+
# - `xbins`, `ybins`: Resolution of the plot
50+
# - `xlims`, `ylims`: Custom axis limits (use `:auto` for automatic)
51+
#
52+
# **For `dalitzprojection`:**
53+
# - First arguments: Mass tuple and intensity function
54+
# - Last argument: Integration function (e.g., `quadgk` from QuadGK.jl)
55+
# - `k`: Which invariant to project onto (1, 2, or 3)
56+
# - `bins`: Number of bins in the projection
57+
# - `xlims`: Custom axis limits (use `:auto` for automatic)
58+
59+
60+
61+
62+
# ## Setting up the model
63+
64+
# For a simple model, we will use a Breit-Wigner lineshape:
65+
struct BW
66+
m::Float64
67+
Γ::Float64
68+
end
69+
(bw::BW)(σ::Number) = 1 / (bw.m^2 - σ - 1im * bw.m * bw.Γ);
70+
71+
# Create decay chains for Lambda resonances:
72+
73+
# parities, needed only to identify available couplings
74+
Ps = ThreeBodyParities('-', '+', '-'; P0 = '+');
75+
76+
const model = ThreeBodyDecay(
77+
["Λ1520", "Λ1690"] .=> zip(
78+
[1.0, 1.5],
79+
[
80+
DecayChainLS(; k = 1, Xlineshape = BW(1.5195, 0.0156), jp = jp"3/2+", Ps, tbs),
81+
DecayChainLS(; k = 1, Xlineshape = BW(1.685, 0.050), jp = jp"1/2+", Ps, tbs),
82+
],
83+
),
84+
);
85+
86+
# ## Dalitz Plot Visualization
87+
88+
# The Dalitz plot shows the intensity distribution across the phase space.
89+
# We can create a Dalitz plot using the `dalitzplot` recipe:
90+
91+
dalitzplot(
92+
masses(model),
93+
Base.Fix1(unpolarized_intensity, model);
94+
iσx = 1,
95+
iσy = 3,
96+
xbins = 100,
97+
ybins = 100,
98+
xlab = "σ₁ = m²(J/ψ p) [GeV²]",
99+
ylab = "σ₃ = m²(p K) [GeV²]",
100+
title = "Dalitz Plot: Λb → J/ψ p K",
101+
)
102+
103+
# You can also specify custom limits:
104+
105+
dalitzplot(
106+
masses(model),
107+
Base.Fix1(unpolarized_intensity, model);
108+
iσx = 2,
109+
iσy = 1,
110+
ylims = (2.0, 3.5),
111+
xbins = 120,
112+
ybins = 120,
113+
xlab = "σ₂ = m²(J/ψ K) [GeV²]",
114+
ylab = "σ₁ = m²(J/ψ p) [GeV²]",
115+
)
116+
117+
# ## Dalitz Plot Projections
118+
119+
# Projections are 1D plots obtained by integrating the Dalitz plot intensity
120+
# over one invariant mass coordinate. This is useful for visualizing features
121+
# that may be clearer in 1D.
122+
123+
# Project onto σ₁ (m²(J/ψ p)):
124+
125+
p1 = dalitzprojection(
126+
masses(model),
127+
Base.Fix1(unpolarized_intensity, model),
128+
quadgk;
129+
k = 1,
130+
bins = 300,
131+
xlims = (2.0, 3.5),
132+
fill = true,
133+
fillalpha = 0.5,
134+
xlab = "σ₁ = m²(J/ψ p) [GeV²]",
135+
ylab = "Integrated Intensity",
136+
title = "Projection onto σ₁ (zoomed)",
137+
)
138+
139+
# Project onto σ₂ (m²(J/ψ K)):
140+
141+
p2 = dalitzprojection(
142+
masses(model),
143+
Base.Fix1(unpolarized_intensity, model),
144+
quadgk;
145+
k = 2,
146+
bins = 100,
147+
fill = true,
148+
fillalpha = 0.5,
149+
xlab = "σ₂ = m²(J/ψ K) [GeV²]",
150+
ylab = "Integrated Intensity",
151+
title = "Projection onto σ₂",
152+
)
153+
154+
# Project onto σ₃ (m²(p K)) with custom limits:
155+
156+
p3 = dalitzprojection(
157+
masses(model),
158+
Base.Fix1(unpolarized_intensity, model),
159+
quadgk;
160+
k = 3,
161+
fill = true,
162+
fillalpha = 0.5,
163+
bins = 150,
164+
xlab = "σ₃ = m²(p K) [GeV²]",
165+
ylab = "Integrated Intensity",
166+
title = "Projection onto σ₃",
167+
)
168+
169+
# ## Comparing Multiple Projections
170+
171+
# You can compare projections from different models or resonances:
172+
173+
plot(
174+
p1,
175+
p2,
176+
p3,
177+
layout = (1, 3),
178+
size = (1200, 400),
179+
bottom_margin = 6Plots.PlotMeasures.mm,
180+
)

src/ThreeBodyDecays.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ include("phase_space.jl")
4242
export border31, border12, border23, border13, border21, border32, border
4343
export DalitzPlot # just a docstring
4444
export dalitzplot # user plot function
45+
export DalitzProjection # just a docstring
46+
export dalitzprojection # user plot function
4547
include("dalitz.jl")
4648

4749
# Wigner Rotations - zeta angles

0 commit comments

Comments
 (0)