Examples for Helicity Angle

Here we use the same config in ex3_particle_config.py

 9 config_str = """
10 decay:
11     A:
12        - [R1, B]
13        - [R2, C]
14        - [R3, D]
15     R1: [C, D]
16     R2: [B, D]
17     R3: [B, C]
18
19 particle:
20     $top:
21        A: { mass: 1.86, J: 0, P: -1}
22     $finals:
23        B: { mass: 0.494, J: 0, P: -1}
24        C: { mass: 0.139, J: 0, P: -1}
25        D: { mass: 0.139, J: 0, P: -1}
26     R1: [ R1_a, R1_b ]
27     R1_a: { mass: 0.7, width: 0.05, J: 1, P: -1}
28     R1_b: { mass: 0.5, width: 0.05, J: 0, P: +1}
29     R2: { mass: 0.824, width: 0.05, J: 0, P: +1}
30     R3: { mass: 0.824, width: 0.05, J: 0, P: +1}
31
32 """
33
34 import matplotlib.pyplot as plt
35 import numpy as np
36 import tensorflow as tf
37 import yaml
38
39 from tf_pwa.config_loader import ConfigLoader
40 from tf_pwa.particle import BaseParticle
41
42 config = ConfigLoader(yaml.full_load(config_str))

TFPWA provide useful class to use helicity angle. we can import it from data_trans module

48 from tf_pwa.data_trans.helicity_angle import HelicityAngle

The class use DecayChain as input. We can get the DecayChain for ConfigLoader

54 decay_chain = config.get_decay(False).get_decay_chain("R1")
55
56 hel = HelicityAngle(decay_chain)

HelcicityAngle provide two main method, find_variable and build_data. find_variable can find the variables in data dict

62 data = config.generate_phsp(1)
63
64 mass, costheta, phi = hel.find_variable(data)
65
66 print(mass)
67 print(costheta)
68 print(phi)
{A: <tf.Tensor: shape=(1,), dtype=float64, numpy=array([1.86])>, R1: <tf.Tensor: shape=(1,), dtype=float64, numpy=array([1.27674445])>, C: <tf.Tensor: shape=(1,), dtype=float64, numpy=array([0.139])>, B: <tf.Tensor: shape=(1,), dtype=float64, numpy=array([0.494])>, D: <tf.Tensor: shape=(1,), dtype=float64, numpy=array([0.139])>}
[<tf.Tensor: shape=(1,), dtype=float64, numpy=array([0.01868061])>, <tf.Tensor: shape=(1,), dtype=float64, numpy=array([-0.50535739])>]
[<tf.Tensor: shape=(1,), dtype=float64, numpy=array([1.11946138])>, <tf.Tensor: shape=(1,), dtype=float64, numpy=array([1.04215005])>]

build_data is the inverse function of cal_angle, it can build 4-momentum from the helicity angles

73 p4 = hel.build_data(mass, costheta, phi)
74
75 print(p4)
{C: <tf.Tensor: shape=(1, 4), dtype=float64, numpy=array([[ 0.58761901, -0.49934137,  0.03417889, -0.27470912]])>, D: <tf.Tensor: shape=(1, 4), dtype=float64, numpy=array([[0.71497249, 0.61194093, 0.19812761, 0.27953249]])>, B: <tf.Tensor: shape=(1, 4), dtype=float64, numpy=array([[ 0.5574085 , -0.11259956, -0.2323065 , -0.00482337]])>}

We can check that the 4-momentums have the same angle as what we input, within the precision range.

80 data2 = hel.cal_angle(p4)
81 mass2, costheta2, phi2 = hel.find_variable(data2)
82
83 print(mass2)
84 print(costheta2)
85 print(phi2)
86
87 assert all(abs(mass[i] - mass2[i]) < 1e-6 for i in mass.keys())
88 assert all(abs(a - b) < 1e-6 for a, b in zip(costheta, costheta2))
89 assert all(abs(a - b) < 1e-6 for a, b in zip(phi, phi2))
{A: <tf.Tensor: shape=(1,), dtype=float64, numpy=array([1.86])>, R1: <tf.Tensor: shape=(1,), dtype=float64, numpy=array([1.27674445])>, C: <tf.Tensor: shape=(1,), dtype=float64, numpy=array([0.139])>, B: <tf.Tensor: shape=(1,), dtype=float64, numpy=array([0.494])>, D: <tf.Tensor: shape=(1,), dtype=float64, numpy=array([0.139])>}
[<tf.Tensor: shape=(1,), dtype=float64, numpy=array([0.01868061])>, <tf.Tensor: shape=(1,), dtype=float64, numpy=array([-0.50535739])>]
[<tf.Tensor: shape=(1,), dtype=float64, numpy=array([1.11946138])>, <tf.Tensor: shape=(1,), dtype=float64, numpy=array([1.04215005])>]

The helicity angle and related masses have the same number of degree of freedom as 4-momentums, it can be used in many cases.

Phase space generator

The most common usage is the Phase Space generator, helicity angle is uniform in phase space. After generate related masses, we can generate uniform random number for helicity angle to build the full 4-momentums. See Phase Space for more information.

 99 from tf_pwa.phasespace import PhaseSpaceGenerator
100
101 gen = PhaseSpaceGenerator(1.86, [0.139, 0.139, 0.494])
102 N = 1
103 mass_gen = gen.generate_mass(N)
104 costheta_gen = [
105     np.random.uniform(-1, 1, size=(N,)),
106     np.random.uniform(-1, 1, size=(N,)),
107 ]
108 phi_gen = [
109     np.random.uniform(-np.pi, np.pi, size=(N,)),
110     np.random.uniform(-np.pi, np.pi, size=(N,)),
111 ]
112
113 p4 = hel.build_data({"R1": mass_gen[0]}, costheta_gen, phi_gen)

Particle Function

We can fix the other variables to see the dependences of one variable.

120 m = np.linspace(0.3, 0.8)
121 mass = {"R1": m}
122 costheta = [np.zeros(m.shape), np.zeros(m.shape)]
123 phi = [np.zeros(m.shape), np.zeros(m.shape)]
124
125 amp = config.get_amplitude()
126 data = config.data.cal_angle(hel.build_data(mass, costheta, phi))
127
128 with amp.temp_used_res(["R1_b"]):
129     prob = amp(data)

There is a useful function get_particle_function provided by ConfigLoader.

134 f = config.get_particle_function("R1_b")
135 prob2 = tf.abs(f(m)[:, 0]) ** 2
136
137 plt.clf()
138 plt.plot(m, prob2.numpy(), label="from particle function")
139 plt.plot(m, prob.numpy(), ls="--", label="from amplitude")
140 plt.legend()
141 plt.xlabel("mass")
142 plt.ylabel("$|A|^2$")
143 plt.show()
ex6 helicity angle

Resolution

Another common usage in TFPWA is used in resolution, when we ony consider resolution in one mass, we can fix the helicity angles, replace the mass into different values, rebuild the 4-monmentums and sum over them to do the integration. See Resolution in Partial Wave Analysis for more information.

156 probs = []
157 delta = np.linspace(-0.2, 0.2, 20)
158 for delta_i in delta:
159     masses_new = mass.copy()
160     new_mass = m + delta_i
161     # be careful for the boundary, the outer value would be NaN
162     new_mass = tf.clip_by_value(
163         new_mass, 0.139 + 0.139 + 1e-6, 1.86 - 0.494 - 1e-6
164     )
165     masses_new[BaseParticle("R1")] = new_mass
166     p4 = hel.build_data(masses_new, costheta, phi)
167     data = config.data.cal_angle(p4)
168     with amp.temp_used_res(["R1_b"]):
169         prob_i = amp(data)
170     probs.append(prob_i)
171
172 gauss = tf.exp(-(delta**2) / 0.05**2 / 2)
173 gauss = gauss / tf.reduce_sum(gauss)
174
175 smear_prob = tf.reduce_sum(tf.stack(probs, axis=-1) * gauss, axis=-1)
176
177 plt.clf()
178 plt.plot(m, smear_prob.numpy(), label="smear $|A|^2$")
179 plt.plot(m, prob.numpy(), ls="--", label="origin $|A|^2$")
180 plt.legend()
181 plt.xlabel("mass")
182 plt.ylabel("$|A|^2$")
183 plt.show()
ex6 helicity angle

Distribution transform

Finally, we can use the helicity angle to study the distribution of other variables.

\[P(y) = \left| \frac{\partial y}{\partial x} \right|^{-1} P(x)\]

The jacobian can be calculated by automatic differentiation.

For example, we can verify that the Dalitz plot variable is uniform distribution. First we generate some variable in phase space.

198 mass, costheta, phi = hel.find_variable(config.generate_phsp(3))
199
200
201 def mass2(p4):
202     return tf.reduce_sum(p4 * p4 * np.array([1, -1, -1, -1]), axis=-1)
203
204
205 b, c, d = [BaseParticle(i) for i in "BCD"]

and than calculate the jacobian

210 x = tf.Variable(tf.stack([mass[BaseParticle("R1")], costheta[1]], axis=-1))
211 with tf.GradientTape() as tape:
212     mass[BaseParticle("R1")] = x[:, 0]
213     costheta[1] = x[:, 1]
214     p4 = hel.build_data(mass, costheta, phi)
215     s12 = mass2(p4[b] + p4[c])
216     s23 = mass2(p4[c] + p4[d])
217     y = tf.stack([s12, s23], axis=-1)
218
219 jac = tape.batch_jacobian(y, x)

These are Dalitz plot variables

224 print("s12, s23 = ", s12, s23)
s12, s23 =  tf.Tensor([1.07086893 0.45713135 1.5645898 ], shape=(3,), dtype=float64) tf.Tensor([1.50300046 0.69188737 0.40849295], shape=(3,), dtype=float64)

the jacobian of the first variable is

229 print(jac[0])
tf.Tensor(
[[-1.04858802  0.58956795]
 [ 2.45193839  0.        ]], shape=(2, 2), dtype=float64)

We can see that the probability density at such points have the same values

234 prob = 1 / tf.abs(tf.linalg.det(jac)) * hel.eval_phsp_factor(mass)
235
236 print("prob = ", prob)
prob =  tf.Tensor([0.1344086 0.1344086 0.1344086], shape=(3,), dtype=float64)

Total running time of the script: (0 minutes 6.630 seconds)

Gallery generated by Sphinx-Gallery