FairShip
Loading...
Searching...
No Matches
hnl.py
Go to the documentation of this file.
1# SPDX-License-Identifier: LGPL-3.0-or-later
2# SPDX-FileCopyrightText: Copyright CERN for the benefit of the SHiP Collaboration
3
4"""
5# ==================================================================
6# Python module
7#
8# This module provides methods to compute the lifetime and
9# branching ratio of HNLs given its mass and couplings as
10# input parameters.
11#
12# Created: 30/11/2014 Elena Graverini (elena.graverini@cern.ch)
13#
14# Updated: 07/10/2017 Kyrylo Bondarenko (bondarenko@lorentz.leidenuniv.nl)
15#
16# Sample usage:
17# ipython -i hnl.py
18# In [1]: b = HNL(1.,[1.e-8, 2.e-8, 1.e-9],True)
19# HNLbranchings instance initialized with couplings:
20# U2e = 1e-08
21# U2mu = 2e-08
22# U2tau = 1e-09
23# and mass:
24# m = 1.0 GeV
25# In [2]: b.computeNLifetime()
26# Out[2]: 4.777721453160521e-05
27# In [3]: b.findBranchingRatio('N -> pi mu')
28# Out[3]: 0.11826749348890987
29#
30# ==================================================================
31"""
32
33import math
34import os
35
36import ROOT
37import shipunit as u
38
39# Load PDG database
40pdg = ROOT.TDatabasePDG.Instance()
41
42
43def PDGname(particle):
44 """
45 Change particle name for use with the PDG database
46 """
47 if "1" in particle:
48 particle = particle.replace("1", "'")
49 if particle == "nu":
50 return "nu_e" # simple workaround to handle 3nu channel
51 return particle
52
53
54def mass(particle: str | None):
55 """
56 Read particle mass from PDG database
57 """
58 particle = PDGname(particle)
59 tPart = pdg.GetParticle(particle)
60 return tPart.Mass()
61
62
63def lifetime(particle):
64 """
65 Read particle lifetime from PDG database
66 """
67 particle = PDGname(particle)
68 tPart = pdg.GetParticle(particle)
69 return tPart.Lifetime()
70
71
73 """
74 CKM matrix, from http://pdg.lbl.gov/2017/reviews/rpp2016-rev-ckm-matrix.pdf
75 """
76
77 def __init__(self) -> None:
78 self.Vud = 0.9743
79 self.Vus = 0.2251
80 self.Vub = 3.6e-03
81 self.Vcd = 0.2249
82 self.Vcs = 0.9735
83 self.Vcb = 4.11e-02
84 self.Vtd = 8.6e-03
85 self.Vts = 4.0e-02
86 self.Vtb = 0.999
87
88
90 """
91 Store some constants useful for HNL physics
92 """
93
94 def __init__(self) -> None:
96 "pi+": 0.130 * u.GeV,
97 "pi0": 0.130 * u.GeV,
98 "eta": 1.2 * 0.130 * u.GeV,
99 "eta1": 0.152 * u.GeV,
100 "eta_c": 0.335 * u.GeV,
101 "rho+": 0.209 * u.GeV,
102 "rho0": 0.209 * u.GeV,
103 "omega": 0.195 * u.GeV,
104 "phi": 0.229 * u.GeV,
105 "D_s+": 0.249 * u.GeV,
106 "D*_s+": 0.315 * u.GeV,
107 } # decay constants f_h of pseudoscalar and vector mesons
108 self.GF = 1.166379e-05 / (u.GeV * u.GeV) # Fermi's constant (GeV^-2)
109 self.s2thetaw = 0.23126 # square sine of the Weinberg angle
110 self.heV = 6.58211928 * pow(10.0, -16) # no units or it messes up!!
111 self.hGeV = self.heV * pow(10.0, -9) # no units or it messes up!!
112
113
114# Load some useful constants
116
117
119 """
120 Lifetime and total and partial decay widths of an HNL
121 """
122
123 def __init__(self, mass, couplings, debug: bool = False) -> None:
124 """
125 Initialize with mass and couplings of the HNL
126
127 Inputs:
128 mass (GeV)
129 couplings (list of [U2e, U2mu, U2tau])
130 """
131 self.U2 = couplings
132 self.U = [math.sqrt(ui) for ui in self.U2]
133 self.MN = mass * u.GeV
134 self.CKM = CKMmatrix()
135 self.CKMelemSq = {
136 "pi+": self.CKM.Vud**2.0,
137 "rho+": self.CKM.Vud**2.0,
138 "D_s+": self.CKM.Vcs**2.0,
139 "D*_s+": self.CKM.Vcs**2.0,
140 "K+": self.CKM.Vus**2.0,
141 # Quarks (up,down)
142 # up: 1=u, 2=c, 3=t
143 # down: 1=d, 2=s, 3=b
144 (1, 1): self.CKM.Vud**2.0,
145 (1, 2): self.CKM.Vus**2.0,
146 (1, 3): self.CKM.Vub**2.0,
147 (2, 1): self.CKM.Vcd**2.0,
148 (2, 2): self.CKM.Vcs**2.0,
149 (2, 3): self.CKM.Vcb**2.0,
150 (3, 1): self.CKM.Vtd**2.0,
151 (3, 2): self.CKM.Vts**2.0,
152 (3, 3): self.CKM.Vtb**2.0,
153 }
154 self.decays = [
155 "N -> nu nu nu",
156 "N -> e- e+ nu_e",
157 "N -> e- e+ nu_mu",
158 "N -> e- e+ nu_tau",
159 "N -> e- mu+ nu_mu",
160 "N -> mu- e+ nu_e",
161 "N -> pi0 nu_e",
162 "N -> pi0 nu_mu",
163 "N -> pi0 nu_tau",
164 "N -> pi+ e-",
165 "N -> mu- mu+ nu_e",
166 "N -> mu- mu+ nu_mu",
167 "N -> mu- mu+ nu_tau",
168 "N -> pi+ mu-",
169 "N -> eta nu_e",
170 "N -> eta nu_mu",
171 "N -> eta nu_tau",
172 "N -> rho0 nu_e",
173 "N -> rho0 nu_mu",
174 "N -> rho0 nu_tau",
175 "N -> rho+ e-",
176 "N -> omega nu_e",
177 "N -> omega nu_mu",
178 "N -> omega nu_tau",
179 "N -> rho+ mu-",
180 "N -> eta1 nu_e",
181 "N -> eta1 nu_mu",
182 "N -> eta1 nu_tau",
183 "N -> phi nu_e",
184 "N -> phi nu_mu",
185 "N -> phi nu_tau",
186 "N -> e- tau+ nu_tau",
187 "N -> tau- e+ nu_e",
188 "N -> mu- tau+ nu_tau",
189 "N -> tau- mu+ nu_mu",
190 "N -> D_s+ e-",
191 "N -> D_s+ mu-",
192 "N -> D*_s+ e-",
193 "N -> D*_s+ mu-",
194 "N -> eta_c nu_e",
195 "N -> eta_c nu_mu",
196 "N -> eta_c nu_tau",
197 ]
198 if self.MN >= 1.0:
200
201 if debug:
202 print("HNLbranchings instance initialized with couplings:")
203 print("\tU2e = %s" % self.U2[0])
204 print("\tU2mu = %s" % self.U2[1])
205 print("\tU2tau = %s" % self.U2[2])
206 print("and mass:")
207 print("\tm = %s GeV" % (self.MN))
208
209 def sqrt_lambda(self, a: float, b, c) -> float | int:
210 """
211 Useful function for decay kinematics. Returns 0 for kinematically forbidden region.
212 This is the Källén (triangle) function λ(a,b,c).
213 """
214 kallen = a**2 + b**2 + c**2 - 2 * a * b - 2 * b * c - 2 * a * c
215 if kallen < 0:
216 return 0
217 else:
218 return math.sqrt(kallen)
219
220 def QCD_correction(self):
221 """
222 Returns 3-loops QCD correction to HNL decay width into quarks
223 """
224 alpha_s = ROOT.TGraph(os.path.expandvars("$FAIRSHIP/python/alpha_s.dat"))
225 a_s = alpha_s.Eval(self.MN)
226 qcd_corr = a_s / math.pi
227 qcd_corr += 5.2 * (a_s / math.pi) ** 2.0
228 qcd_corr += 26.4 * (a_s / math.pi) ** 3.0
229 return qcd_corr
230
231 def Width_3nu(self):
232 """
233 Returns the HNL decay width into three neutrinos
234 """
235 width = (c.GF**2.0) * (self.MN**5.0) * sum(self.U2) / (192.0 * (u.pi**3.0))
236 width = 2.0 * width # Majorana case (charge conjugate channels)
237 return width
238
239 def Width_nu_f_fbar(self, alpha: int, beta: int):
240 """
241 Returns the HNL tree level decay width into the fermion-antifermion pair and a neutrino (decay through Z boson or Z and W)
242
243 Inputs:
244 - alpha is a flavour of neutrino (1,2 or 3), determine the mixing angle U_alpha
245 - beta is a flavour of the fermions: (1, 2 or 3) for charge leptons or (4, 5, 6, 7, 8, 9) for quarks
246 """
247 if (alpha not in [1, 2, 3]) or (beta not in [1, 2, 3, 4, 5, 6, 7, 8, 9]):
248 print("Width_nu_f_fbar ERROR: unknown channel alpha =", alpha, " beta =", beta)
249 quit()
250 fermions = [None, "e-", "mu-", "tau-", "u", "d", "s", "c", "b", "t"]
251 x = mass(fermions[beta]) / self.MN
252 if x > 0.5: # the decay is kinematically forbidden
253 return 0
254 width = (c.GF**2.0) * (self.MN**5.0) * self.U2[alpha - 1] / (192.0 * (math.pi**3.0))
255 L = 0.0
256 if x > 0.01:
257 logContent = (1.0 - 3.0 * x**2.0 - (1.0 - x**2.0) * math.sqrt(1.0 - 4.0 * x**2.0)) / (
258 (x**2.0) * (1 + math.sqrt(1.0 - 4.0 * x**2.0))
259 )
260 else:
261 logContent = x**4 + 4.0 * x**6 + 14.0 * x**8
262 if logContent > 0:
263 L = math.log(logContent)
264 if beta < 4: # lepton decay
265 NZ = 1
266 if alpha == beta: # interference case
267 C1 = 0.25 * (1.0 + 4.0 * c.s2thetaw + 8.0 * c.s2thetaw**2.0)
268 C2 = 0.5 * c.s2thetaw * (2.0 * c.s2thetaw + 1.0)
269 else: # no interference
270 C1 = 0.25 * (1.0 - 4.0 * c.s2thetaw + 8.0 * c.s2thetaw**2.0)
271 C2 = 0.5 * c.s2thetaw * (2.0 * c.s2thetaw - 1.0)
272 else: # decay into quarks
273 NZ = 3
274 if beta in [4, 7, 9]: # up quarks
275 C1 = 0.25 * (1.0 - 8.0 / 3.0 * c.s2thetaw + 32.0 / 9.0 * c.s2thetaw**2.0)
276 C2 = 1.0 / 3.0 * c.s2thetaw * (4.0 / 3.0 * c.s2thetaw - 1.0)
277 if beta in [5, 6, 8]: # down quarks
278 C1 = 0.25 * (1.0 - 4.0 / 3.0 * c.s2thetaw + 8.0 / 9.0 * c.s2thetaw**2.0)
279 C2 = 1.0 / 6.0 * c.s2thetaw * (2.0 / 3.0 * c.s2thetaw - 1.0)
280 width = (
281 width
282 * NZ
283 * (
284 C1
285 * (
286 (1.0 - 14.0 * x**2.0 - 2.0 * x**4.0 - 12.0 * x**6.0) * math.sqrt(1 - 4.0 * x**2)
287 + 12.0 * x**4.0 * (-1.0 + x**4.0) * L
288 )
289 + 4.0
290 * C2
291 * (
292 x**2.0 * (2.0 + 10.0 * x**2.0 - 12.0 * x**4.0) * math.sqrt(1.0 - 4.0 * x**2)
293 + 6.0 * x**4.0 * (1.0 - 2.0 * x**2 + 2.0 * x**4) * L
294 )
295 )
296 )
297 width = 2.0 * width # Majorana case (charge conjugate channels)
298 return width
299
300 def Integrand(self, xx, xi):
301 """
302 Function to integrate needed for numerical integration using ROOT. Needed for 3-body decays through W boson.
303 First argument is a list of 1 number, argument. Second is the list of 3 numbers xi = mi/MN
304 """
305 x = xx[0]
306 xl2 = xi[0] ** 2
307 xu2 = xi[1] ** 2
308 xd2 = xi[2] ** 2
309 if x == 0: # Workaround for division by zero
310 return 0
311 res = 1.0 / x
312 res *= x - xl2 - xd2
313 res *= 1.0 + xu2 - x
314 res *= self.sqrt_lambda(x, xl2, xd2)
315 res *= self.sqrt_lambda(1.0, x, xu2)
316 return res
317
318 def integral(self, x1, x2, x3: int):
319 """
320 Numerical integral needed for 3-body decays through W boson.
321 xi = mi/MN
322 """
323 theFunction = self.Integrand
324 func = ROOT.TF1("func", theFunction, 0, 1, 3) # Setting function
325 func.SetParameters(x1, x2, x3)
326 xmin = (x1 + x3) ** 2
327 xmax = (1.0 - x2) ** 2
328 wf1 = ROOT.Math.WrappedTF1(func) # Setting simple Gauss integration method
329 ig = ROOT.Math.GaussIntegrator()
330 ig.SetFunction(wf1)
331 ig.SetRelTolerance(0.001)
332 res = 12.0 * ig.Integral(xmin, xmax)
333 return res
334
335 def Width_l1_l2_nu2(self, alpha: int, beta: int):
336 """
337 Returns the HNL decay width into two different flavour leptons and a neutrino (decay through W boson)
338
339 Inputs:
340 - alpha is a flavour of the first lepton (1,2 or 3), determine the mixing angle U_alpha
341 - beta is a flavour of the second lepton and neutrino (1, 2 or 3)
342 """
343 if (alpha not in [1, 2, 3]) or (beta not in [1, 2, 3]):
344 print("Width_l1_l2_nu2 ERROR: unknown channel alpha =", alpha, " beta =", beta)
345 quit()
346 if (
347 alpha == beta
348 ): # The interference case is handled by Width_nu_f_fbar function, workaround for a total width calculation
349 return 0
350 leptons = [None, "e-", "mu-", "tau-"]
351 x1 = mass(leptons[alpha]) / self.MN
352 x2 = mass(leptons[beta]) / self.MN
353 if x1 + x2 > 1: # The decay is kinematically forbidden
354 return 0
355 width = (c.GF**2.0) * (self.MN**5.0) * self.U2[alpha - 1] / (192.0 * (math.pi**3.0))
356 width = width * self.integral(x1, x2, 0)
357 width = 2.0 * width # Majorana case (charge conjugate channels)
358 return width
359
360 def Width_l_u_d(self, alpha: int, beta: int, gamma: int):
361 """
362 Returns the HNL tree level decay width into a charged lepton, up quark and down quark (decay through W boson)
363
364 Inputs:
365 - alpha is a flavour of the charged lepton (1,2 or 3), determine the mixing angle U_alpha
366 - beta is the up quark generation (1, 2, 3 for u, c, t)
367 - gamma is the down quark generation (1, 2, 3 for d, s, b)
368 """
369 if (alpha not in [1, 2, 3]) or (beta not in [1, 2, 3]) or (gamma not in [1, 2, 3]):
370 print("Width_l_u_d ERROR: unknown channel alpha =", alpha, " beta =", beta, " gamma =", gamma)
371 quit()
372 leptons = [None, "e-", "mu-", "tau-"]
373 up_quarks = [None, "u", "c", "t"]
374 down_quarks = [None, "d", "s", "b"]
375 xl = mass(leptons[alpha]) / self.MN
376 xu = mass(up_quarks[beta]) / self.MN
377 xd = mass(down_quarks[gamma]) / self.MN
378 if xl + xu + xd > 1: # The decay is kinematically forbidden
379 return 0
380 width = (c.GF**2.0) * (self.MN**5.0) * self.U2[alpha - 1] / (192.0 * (math.pi**3.0))
381 width *= 3 * self.CKMelemSq[(beta, gamma)]
382 width *= self.integral(xl, xu, xd)
383 width = 2.0 * width # Majorana case (charge conjugate channels)
384 return width
385
386 def Width_H0_nu(self, H: str, alpha: int):
387 """
388 Returns the HNL decay width into neutral meson and neutrino
389
390 Inputs:
391 - H is a string (name of the meson)
392 - alpha is a flavour of the nu (1, 2 or 3), determine the mixing angle U_alpha
393 """
394 if (H not in ["pi0", "eta", "rho0", "omega", "eta1", "phi", "eta_c"]) or (alpha not in [1, 2, 3]):
395 print("Width_H0_nu ERROR: unknown channel H0 =", H, " alpha =", alpha)
396 quit()
397 x = mass(H) / self.MN
398 if x > 1: # The decay is kinematically forbidden
399 return 0.0
400 width = (c.GF**2.0) * (c.decayConstant[H] ** 2.0) * (self.MN**3.0) * self.U2[alpha - 1] / (32.0 * math.pi)
401 if H in ["pi0", "eta", "eta1", "eta_c"]: # pseudoscalar mesons
402 width *= (1 - x**2.0) ** 2.0
403 if H in ["rho0", "omega", "phi"]: # vector mesons
404 width *= (1.0 + 2 * x**2.0) * (1.0 - x**2.0) ** 2.0
405 if H == "rho0":
406 width *= (1.0 - 2.0 * c.s2thetaw) ** 2.0
407 if H == "omega":
408 width *= (16.0 * c.s2thetaw**2.0) / 9.0
409 if H == "phi":
410 width *= (1.0 - 4.0 / 3.0 * c.s2thetaw) ** 2.0
411 width = 2.0 * width # Majorana case (charge conjugate channels)
412 return width
413
414 def Width_H_l(self, H: str, alpha: int):
415 """
416 Returns the HNL decay width into charged meson and lepton
417
418 Inputs:
419 - H is a string (name of the positively charged meson)
420 - alpha is the lepton flavour (1, 2 or 3), determine the mixing angle U_alpha
421 """
422 if (H not in ["pi+", "rho+", "D_s+", "D*_s+"]) or (alpha not in [1, 2, 3]):
423 print("Width_H_l ERROR: unknown channel H =", H, " alpha =", alpha)
424 quit()
425 leptons = [None, "e-", "mu-", "tau-"]
426 xl = mass(leptons[alpha]) / self.MN
427 xh = mass(H) / self.MN
428 if xl + xh > 1: # The decay is kinematically forbidden
429 return 0.0
430 width = (
431 (c.GF**2.0)
432 * (c.decayConstant[H] ** 2.0)
433 * self.CKMelemSq[H]
434 * (self.MN**3.0)
435 * self.U2[alpha - 1]
436 / (16.0 * math.pi)
437 )
438 width *= self.sqrt_lambda(1.0, xl**2, xh**2)
439 if H in ["pi+", "D_s+"]: # pseudoscalar mesons
440 width *= (1.0 - xl**2.0) ** 2.0 - xh**2.0 * (1.0 + xl**2.0)
441 if H in ["rho+", "D*_s+"]: # vector mesons
442 width *= (1.0 - xl**2.0) ** 2.0 + xh**2.0 * (1.0 + xl**2.0) - 2.0 * xh**4.0
443 width = 2.0 * width # Majorana case (charge conjugate channels)
444 return width
445
446 def Width_charged_leptons(self) -> float:
447 """
448 Returns the total HNL leptonic decay width with charged leptons in final state
449 """
450 width = 0.0
451 for l1 in [1, 2, 3]:
452 for l2 in [1, 2, 3]:
453 width += self.Width_nu_f_fbar(l1, l2)
454 width += self.Width_l1_l2_nu2(l1, l2)
455 return width
456
457 def Width_neutral_mesons(self) -> float:
458 """
459 Returns the total HNL decay width into a neutral meson and a neutrino
460 """
461 mesons = ["pi0", "eta", "rho0", "omega", "eta1", "phi", "eta_c"]
462 width = 0.0
463 for m in mesons:
464 for lepton in [1, 2, 3]:
465 width += self.Width_H0_nu(m, lepton)
466 return width
467
468 def Width_charged_mesons(self) -> float:
469 """
470 Returns the total HNL decay width into a charged meson and a charged lepton
471 """
472 mesons = ["pi+", "rho+", "D_s+", "D*_s+"]
473 width = 0.0
474 for m in mesons:
475 for lepton in [1, 2, 3]:
476 width += self.Width_H_l(m, lepton)
477 return width
478
479 def Width_quarks_neutrino(self) -> float | int:
480 """
481 Returns the total HNL decay width with quarks and neutrino in final state. Uses 3-loops alpha_s correction
482 """
483 if self.MN < 1.0: # decay into quarks is not applicable at low HNL mass
484 return 0
485 width = 0.0
486 for lepton in [1, 2, 3]:
487 for q in [4, 5, 6, 7, 8, 9]:
488 width += self.Width_nu_f_fbar(lepton, q)
489 width *= 1.0 + self.QCD_corr
490 return width
491
492 def Width_quarks_lepton(self) -> float | int:
493 """
494 Returns the total HNL decay width with quarks and charged lepton in final state. Uses 3-loops alpha_s correction
495 """
496 if self.MN < 1.0: # decay into quarks is not applicable at low HNL mass
497 return 0
498 width = 0.0
499 for lepton in [1, 2, 3]:
500 for u_quark in [1, 2, 3]:
501 for d_quark in [1, 2, 3]:
502 width += self.Width_l_u_d(lepton, u_quark, d_quark)
503 width *= 1.0 + self.QCD_corr
504 return width
505
506 def NDecayWidth(self):
507 """
508 Returns the total HNL decay width
509 """
510 leptonWidth = self.Width_3nu() + self.Width_charged_leptons()
511 mesonWidth = self.Width_neutral_mesons() + self.Width_charged_mesons()
512 quarkWidth = self.Width_quarks_neutrino() + self.Width_quarks_lepton()
513 totalWidth = leptonWidth + max([mesonWidth, quarkWidth])
514 return totalWidth
515
516 def findBranchingRatio(self, decayString):
517 """
518 Returns the branching ratio of the selected HNL decay channel
519
520 Inputs:
521 - decayString is a string describing the decay, in the form 'N -> stuff1 ... stuffN'
522 """
523 br = 0.0
524 totalWidth = self.NDecayWidth()
525
526 if (decayString not in self.decays) and (decayString not in ["N -> hadrons", "N -> charged hadrons"]):
527 print("findBranchingRatio ERROR: unknown decay %s" % decayString)
528 quit()
529
530 if decayString == "N -> nu nu nu" or decayString == "N -> 3nu":
531 br = self.Width_3nu() / totalWidth # inclusive
532 if decayString == "N -> e- e+ nu_e":
533 br = self.Width_nu_f_fbar(1, 1) / totalWidth
534 if decayString == "N -> e- e+ nu_mu":
535 br = self.Width_nu_f_fbar(2, 1) / totalWidth
536 if decayString == "N -> e- e+ nu_tau":
537 br = self.Width_nu_f_fbar(3, 1) / totalWidth
538 if decayString == "N -> e- mu+ nu_mu":
539 br = self.Width_l1_l2_nu2(1, 2) / totalWidth
540 if decayString == "N -> mu- e+ nu_e":
541 br = self.Width_l1_l2_nu2(2, 1) / totalWidth
542 if decayString == "N -> pi0 nu_e":
543 br = self.Width_H0_nu("pi0", 1) / totalWidth
544 if decayString == "N -> pi0 nu_mu":
545 br = self.Width_H0_nu("pi0", 2) / totalWidth
546 if decayString == "N -> pi0 nu_tau":
547 br = self.Width_H0_nu("pi0", 3) / totalWidth
548 if decayString == "N -> pi+ e-":
549 br = self.Width_H_l("pi+", 1) / totalWidth
550 if decayString == "N -> mu- mu+ nu_e":
551 br = self.Width_nu_f_fbar(1, 2) / totalWidth
552 if decayString == "N -> mu- mu+ nu_mu":
553 br = self.Width_nu_f_fbar(2, 2) / totalWidth
554 if decayString == "N -> mu- mu+ nu_tau":
555 br = self.Width_nu_f_fbar(3, 2) / totalWidth
556 if decayString == "N -> pi+ mu-":
557 br = self.Width_H_l("pi+", 2) / totalWidth
558 if decayString == "N -> eta nu_e":
559 br = self.Width_H0_nu("eta", 1) / totalWidth
560 if decayString == "N -> eta nu_mu":
561 br = self.Width_H0_nu("eta", 2) / totalWidth
562 if decayString == "N -> eta nu_tau":
563 br = self.Width_H0_nu("eta", 3) / totalWidth
564 if decayString == "N -> rho0 nu_e":
565 br = self.Width_H0_nu("rho0", 1) / totalWidth
566 if decayString == "N -> rho0 nu_mu":
567 br = self.Width_H0_nu("rho0", 2) / totalWidth
568 if decayString == "N -> rho0 nu_tau":
569 br = self.Width_H0_nu("rho0", 3) / totalWidth
570 if decayString == "N -> rho+ e-":
571 br = self.Width_H_l("rho+", 1) / totalWidth
572 if decayString == "N -> omega nu_e":
573 br = self.Width_H0_nu("omega", 1) / totalWidth
574 if decayString == "N -> omega nu_mu":
575 br = self.Width_H0_nu("omega", 2) / totalWidth
576 if decayString == "N -> omega nu_tau":
577 br = self.Width_H0_nu("omega", 3) / totalWidth
578 if decayString == "N -> rho+ mu-":
579 br = self.Width_H_l("rho+", 2) / totalWidth
580 if decayString == "N -> eta1 nu_e":
581 br = self.Width_H0_nu("eta1", 1) / totalWidth
582 if decayString == "N -> eta1 nu_mu":
583 br = self.Width_H0_nu("eta1", 2) / totalWidth
584 if decayString == "N -> eta1 nu_tau":
585 br = self.Width_H0_nu("eta1", 3) / totalWidth
586 if decayString == "N -> phi nu_e":
587 br = self.Width_H0_nu("phi", 1) / totalWidth
588 if decayString == "N -> phi nu_mu":
589 br = self.Width_H0_nu("phi", 2) / totalWidth
590 if decayString == "N -> phi nu_tau":
591 br = self.Width_H0_nu("phi", 3) / totalWidth
592 if decayString == "N -> e- tau+ nu_tau":
593 br = self.Width_l1_l2_nu2(1, 3) / totalWidth
594 if decayString == "N -> tau- e+ nu_e":
595 br = self.Width_l1_l2_nu2(3, 1) / totalWidth
596 if decayString == "N -> mu- tau+ nu_tau":
597 br = self.Width_l1_l2_nu2(2, 3) / totalWidth
598 if decayString == "N -> tau- mu+ nu_mu":
599 br = self.Width_l1_l2_nu2(3, 2) / totalWidth
600 if decayString == "N -> D_s+ e-":
601 br = self.Width_H_l("D_s+", 1) / totalWidth
602 if decayString == "N -> D_s+ mu-":
603 br = self.Width_H_l("D_s+", 2) / totalWidth
604 if decayString == "N -> D*_s+ e-":
605 br = self.Width_H_l("D*_s+", 1) / totalWidth
606 if decayString == "N -> D*_s+ mu-":
607 br = self.Width_H_l("D*_s+", 2) / totalWidth
608 if decayString == "N -> eta_c nu_e":
609 br = self.Width_H0_nu("eta_c", 1) / totalWidth
610 if decayString == "N -> eta_c nu_mu":
611 br = self.Width_H0_nu("eta_c", 2) / totalWidth
612 if decayString == "N -> eta_c nu_tau":
613 br = self.Width_H0_nu("eta_c", 3) / totalWidth
614
615 if decayString == "N -> hadrons":
616 mesonWidth = self.Width_neutral_mesons() + self.Width_charged_mesons()
617 quarkWidth = self.Width_quarks_neutrino() + self.Width_quarks_lepton()
618 br = max([mesonWidth, quarkWidth]) / totalWidth
619 if decayString == "N -> charged hadrons":
620 br = max([self.Width_charged_mesons(), self.Width_quarks_lepton()]) / totalWidth
621 return br
622
623 def allowedChannels(self) -> dict[str, str]:
624 """
625 Returns a dictionary of kinematically allowed decay channels
626
627 Inputs:
628 - decayString is a string describing the decay, in the form 'N -> stuff1 ... stuffN'
629 """
630 m = self.MN
631 allowedDecays = {"N -> nu nu nu": "yes"}
632 if m > 2.0 * mass("e-"):
633 allowedDecays.update({"N -> e- e+ nu_e": "yes"})
634 allowedDecays.update({"N -> e- e+ nu_mu": "yes"})
635 allowedDecays.update({"N -> e- e+ nu_tau": "yes"})
636 if m > mass("e-") + mass("mu-"):
637 allowedDecays.update({"N -> e- mu+ nu_mu": "yes"})
638 allowedDecays.update({"N -> mu- e+ nu_e": "yes"})
639 if m > mass("pi0"):
640 allowedDecays.update({"N -> pi0 nu_e": "yes"})
641 allowedDecays.update({"N -> pi0 nu_mu": "yes"})
642 allowedDecays.update({"N -> pi0 nu_tau": "yes"})
643 if m > mass("pi+") + mass("e-"):
644 allowedDecays.update({"N -> pi+ e-": "yes"})
645 if m > 2.0 * mass("mu-"):
646 allowedDecays.update({"N -> mu- mu+ nu_e": "yes"})
647 allowedDecays.update({"N -> mu- mu+ nu_mu": "yes"})
648 allowedDecays.update({"N -> mu- mu+ nu_tau": "yes"})
649 if m > mass("pi+") + mass("mu-"):
650 allowedDecays.update({"N -> pi+ mu-": "yes"})
651 if m > mass("eta"):
652 allowedDecays.update({"N -> eta nu_e": "yes"})
653 allowedDecays.update({"N -> eta nu_mu": "yes"})
654 allowedDecays.update({"N -> eta nu_tau": "yes"})
655 if m > mass("rho0"):
656 allowedDecays.update({"N -> rho0 nu_e": "yes"})
657 allowedDecays.update({"N -> rho0 nu_mu": "yes"})
658 allowedDecays.update({"N -> rho0 nu_tau": "yes"})
659 if m > mass("rho+") + mass("e-"):
660 allowedDecays.update({"N -> rho+ e-": "yes"})
661 if m > mass("omega"):
662 allowedDecays.update({"N -> omega nu_e": "yes"})
663 allowedDecays.update({"N -> omega nu_mu": "yes"})
664 allowedDecays.update({"N -> omega nu_tau": "yes"})
665 if m > mass("rho+") + mass("mu-"):
666 allowedDecays.update({"N -> rho+ mu-": "yes"})
667 if m > mass("eta1"):
668 allowedDecays.update({"N -> eta1 nu_e": "yes"})
669 allowedDecays.update({"N -> eta1 nu_mu": "yes"})
670 allowedDecays.update({"N -> eta1 nu_tau": "yes"})
671 if m > mass("phi"):
672 allowedDecays.update({"N -> phi nu_e": "yes"})
673 allowedDecays.update({"N -> phi nu_mu": "yes"})
674 allowedDecays.update({"N -> phi nu_tau": "yes"})
675 if m > mass("e-") + mass("tau-"):
676 allowedDecays.update({"N -> e- tau+ nu_tau": "yes"})
677 allowedDecays.update({"N -> tau- e+ nu_e": "yes"})
678 if m > mass("mu-") + mass("tau-"):
679 allowedDecays.update({"N -> mu- tau+ nu_tau": "yes"})
680 allowedDecays.update({"N -> tau- mu+ nu_mu": "yes"})
681 if m > mass("D_s+") + mass("e-"):
682 allowedDecays.update({"N -> D_s+ e-": "yes"})
683 if m > mass("D_s+") + mass("mu-"):
684 allowedDecays.update({"N -> D_s+ mu-": "yes"})
685 if m > mass("D*_s+") + mass("e-"):
686 allowedDecays.update({"N -> D*_s+ e-": "yes"})
687 if m > mass("D*_s+") + mass("mu-"):
688 allowedDecays.update({"N -> D*_s+ mu-": "yes"})
689 if m > mass("eta_c"):
690 allowedDecays.update({"N -> eta_c nu_e": "yes"})
691 allowedDecays.update({"N -> eta_c nu_mu": "yes"})
692 allowedDecays.update({"N -> eta_c nu_tau": "yes"})
693
694 for decay in self.decays:
695 if decay not in allowedDecays:
696 allowedDecays.update({decay: "no"})
697 return allowedDecays
698
699
701 """
702 HNL physics according to the nuMSM
703 """
704
705 def __init__(self, mass, couplings, debug: bool = False) -> None:
706 """
707 Initialize with mass and couplings of the HNL
708
709 Inputs:
710 mass (GeV)
711 couplings (list of [U2e, U2mu, U2tau])
712 """
713 HNLbranchings.__init__(self, mass, couplings, debug)
714
715 def computeNLifetime(self, system: str = "SI"):
716 """
717 Compute the HNL lifetime
718
719 Inputs:
720 - system: choose between default (i.e. SI, result in s) or FairShip (result in ns)
721 """
722 self.NLifetime = c.hGeV / self.NDecayWidth()
723 if system == "FairShip":
724 self.NLifetime *= 1.0e9
725 return self.NLifetime
None __init__(self)
Definition: hnl.py:77
Definition: hnl.py:700
def computeNLifetime(self, str system="SI")
Definition: hnl.py:715
None __init__(self, mass, couplings, bool debug=False)
Definition: hnl.py:705
NLifetime
Definition: hnl.py:722
float Width_neutral_mesons(self)
Definition: hnl.py:457
dict[str, str] allowedChannels(self)
Definition: hnl.py:623
float|int Width_quarks_neutrino(self)
Definition: hnl.py:479
def NDecayWidth(self)
Definition: hnl.py:506
def Width_l_u_d(self, int alpha, int beta, int gamma)
Definition: hnl.py:360
def Width_3nu(self)
Definition: hnl.py:231
def Width_nu_f_fbar(self, int alpha, int beta)
Definition: hnl.py:239
None __init__(self, mass, couplings, bool debug=False)
Definition: hnl.py:123
float|int sqrt_lambda(self, float a, b, c)
Definition: hnl.py:209
def integral(self, x1, x2, int x3)
Definition: hnl.py:318
def Width_H_l(self, str H, int alpha)
Definition: hnl.py:414
def findBranchingRatio(self, decayString)
Definition: hnl.py:516
def Width_l1_l2_nu2(self, int alpha, int beta)
Definition: hnl.py:335
float Width_charged_leptons(self)
Definition: hnl.py:446
float Width_charged_mesons(self)
Definition: hnl.py:468
def Width_H0_nu(self, str H, int alpha)
Definition: hnl.py:386
def Integrand(self, xx, xi)
Definition: hnl.py:300
def QCD_correction(self)
Definition: hnl.py:220
float|int Width_quarks_lepton(self)
Definition: hnl.py:492
None __init__(self)
Definition: hnl.py:94
decayConstant
Definition: hnl.py:95
def lifetime(particle)
Definition: hnl.py:63