5# ==================================================================
40pdg = ROOT.TDatabasePDG.Instance()
45 Change particle name for use
with the PDG database
48 particle = particle.replace(
"1",
"'")
54def mass(particle: str |
None):
56 Read particle mass from PDG database
58 particle = PDGname(particle)
59 tPart = pdg.GetParticle(particle)
65 Read particle lifetime from PDG database
67 particle = PDGname(particle)
68 tPart = pdg.GetParticle(particle)
69 return tPart.Lifetime()
74 CKM matrix, from http://pdg.lbl.gov/2017/reviews/rpp2016-rev-ckm-matrix.pdf
91 Store some constants useful for HNL physics
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,
108 self.
GF = 1.166379e-05 / (u.GeV * u.GeV)
110 self.
heV = 6.58211928 * pow(10.0, -16)
120 Lifetime and total
and partial decay widths of an HNL
123 def __init__(self, mass, couplings, debug: bool =
False) ->
None:
125 Initialize with mass
and couplings of the HNL
129 couplings (list of [U2e, U2mu, U2tau])
132 self.U = [math.sqrt(ui) for ui
in self.
U2]
133 self.
MN = mass * u.GeV
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,
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,
166 "N -> mu- mu+ nu_mu",
167 "N -> mu- mu+ nu_tau",
186 "N -> e- tau+ nu_tau",
188 "N -> mu- tau+ nu_tau",
189 "N -> tau- mu+ nu_mu",
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])
207 print(
"\tm = %s GeV" % (self.
MN))
211 Useful function for decay kinematics. Returns 0
for kinematically forbidden region.
212 This
is the Källén (triangle) function λ(a,b,c).
214 kallen = a**2 + b**2 + c**2 - 2 * a * b - 2 * b * c - 2 * a * c
218 return math.sqrt(kallen)
222 Returns 3-loops QCD correction to HNL decay width into quarks
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
233 Returns the HNL decay width into three neutrinos
235 width = (c.GF**2.0) * (self.MN**5.0) * sum(self.U2) / (192.0 * (u.pi**3.0))
241 Returns the HNL tree level decay width into the fermion-antifermion pair and a neutrino (decay through Z boson
or Z
and W)
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
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)
250 fermions = [
None,
"e-",
"mu-",
"tau-",
"u",
"d",
"s",
"c",
"b",
"t"]
251 x = mass(fermions[beta]) / self.
MN
254 width = (c.GF**2.0) * (self.
MN**5.0) * self.
U2[alpha - 1] / (192.0 * (math.pi**3.0))
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))
261 logContent = x**4 + 4.0 * x**6 + 14.0 * x**8
263 L = math.log(logContent)
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)
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)
274 if beta
in [4, 7, 9]:
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]:
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)
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
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
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
320 Numerical integral needed for 3-body decays through W boson.
324 func = ROOT.TF1("func", theFunction, 0, 1, 3)
325 func.SetParameters(x1, x2, x3)
326 xmin = (x1 + x3) ** 2
327 xmax = (1.0 - x2) ** 2
328 wf1 = ROOT.Math.WrappedTF1(func)
329 ig = ROOT.Math.GaussIntegrator()
331 ig.SetRelTolerance(0.001)
332 res = 12.0 * ig.Integral(xmin, xmax)
337 Returns the HNL decay width into two different flavour leptons and a neutrino (decay through W boson)
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)
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)
350 leptons = [
None,
"e-",
"mu-",
"tau-"]
351 x1 = mass(leptons[alpha]) / self.
MN
352 x2 = mass(leptons[beta]) / self.
MN
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)
362 Returns the HNL tree level decay width into a charged lepton, up quark and down quark (decay through W boson)
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)
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)
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
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)]
388 Returns the HNL decay width into neutral meson and neutrino
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
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)
397 x = mass(H) / self.
MN
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"]:
402 width *= (1 - x**2.0) ** 2.0
403 if H
in [
"rho0",
"omega",
"phi"]:
404 width *= (1.0 + 2 * x**2.0) * (1.0 - x**2.0) ** 2.0
406 width *= (1.0 - 2.0 * c.s2thetaw) ** 2.0
408 width *= (16.0 * c.s2thetaw**2.0) / 9.0
410 width *= (1.0 - 4.0 / 3.0 * c.s2thetaw) ** 2.0
416 Returns the HNL decay width into charged meson and lepton
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
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)
425 leptons = [
None,
"e-",
"mu-",
"tau-"]
426 xl = mass(leptons[alpha]) / self.
MN
427 xh = mass(H) / self.
MN
432 * (c.decayConstant[H] ** 2.0)
439 if H
in [
"pi+",
"D_s+"]:
440 width *= (1.0 - xl**2.0) ** 2.0 - xh**2.0 * (1.0 + xl**2.0)
441 if H
in [
"rho+",
"D*_s+"]:
442 width *= (1.0 - xl**2.0) ** 2.0 + xh**2.0 * (1.0 + xl**2.0) - 2.0 * xh**4.0
448 Returns the total HNL leptonic decay width with charged leptons
in final state
459 Returns the total HNL decay width into a neutral meson and a neutrino
461 mesons = ["pi0",
"eta",
"rho0",
"omega",
"eta1",
"phi",
"eta_c"]
464 for lepton
in [1, 2, 3]:
470 Returns the total HNL decay width into a charged meson and a charged lepton
472 mesons = ["pi+",
"rho+",
"D_s+",
"D*_s+"]
475 for lepton
in [1, 2, 3]:
481 Returns the total HNL decay width with quarks
and neutrino
in final state. Uses 3-loops alpha_s correction
486 for lepton
in [1, 2, 3]:
487 for q
in [4, 5, 6, 7, 8, 9]:
494 Returns the total HNL decay width with quarks
and charged lepton
in final state. Uses 3-loops alpha_s correction
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)
508 Returns the total HNL decay width
513 totalWidth = leptonWidth + max([mesonWidth, quarkWidth])
518 Returns the branching ratio of the selected HNL decay channel
521 - decayString is a string describing the decay,
in the form
'N -> stuff1 ... stuffN'
526 if (decayString
not in self.
decays)
and (decayString
not in [
"N -> hadrons",
"N -> charged hadrons"]):
527 print(
"findBranchingRatio ERROR: unknown decay %s" % decayString)
530 if decayString ==
"N -> nu nu nu" or decayString ==
"N -> 3nu":
532 if decayString ==
"N -> e- e+ nu_e":
534 if decayString ==
"N -> e- e+ nu_mu":
536 if decayString ==
"N -> e- e+ nu_tau":
538 if decayString ==
"N -> e- mu+ nu_mu":
540 if decayString ==
"N -> mu- e+ nu_e":
542 if decayString ==
"N -> pi0 nu_e":
544 if decayString ==
"N -> pi0 nu_mu":
546 if decayString ==
"N -> pi0 nu_tau":
548 if decayString ==
"N -> pi+ e-":
549 br = self.
Width_H_l(
"pi+", 1) / totalWidth
550 if decayString ==
"N -> mu- mu+ nu_e":
552 if decayString ==
"N -> mu- mu+ nu_mu":
554 if decayString ==
"N -> mu- mu+ nu_tau":
556 if decayString ==
"N -> pi+ mu-":
557 br = self.
Width_H_l(
"pi+", 2) / totalWidth
558 if decayString ==
"N -> eta nu_e":
560 if decayString ==
"N -> eta nu_mu":
562 if decayString ==
"N -> eta nu_tau":
564 if decayString ==
"N -> rho0 nu_e":
566 if decayString ==
"N -> rho0 nu_mu":
568 if decayString ==
"N -> rho0 nu_tau":
570 if decayString ==
"N -> rho+ e-":
571 br = self.
Width_H_l(
"rho+", 1) / totalWidth
572 if decayString ==
"N -> omega nu_e":
574 if decayString ==
"N -> omega nu_mu":
576 if decayString ==
"N -> omega nu_tau":
578 if decayString ==
"N -> rho+ mu-":
579 br = self.
Width_H_l(
"rho+", 2) / totalWidth
580 if decayString ==
"N -> eta1 nu_e":
582 if decayString ==
"N -> eta1 nu_mu":
584 if decayString ==
"N -> eta1 nu_tau":
586 if decayString ==
"N -> phi nu_e":
588 if decayString ==
"N -> phi nu_mu":
590 if decayString ==
"N -> phi nu_tau":
592 if decayString ==
"N -> e- tau+ nu_tau":
594 if decayString ==
"N -> tau- e+ nu_e":
596 if decayString ==
"N -> mu- tau+ nu_tau":
598 if decayString ==
"N -> tau- mu+ nu_mu":
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":
610 if decayString ==
"N -> eta_c nu_mu":
612 if decayString ==
"N -> eta_c nu_tau":
615 if decayString ==
"N -> hadrons":
618 br = max([mesonWidth, quarkWidth]) / totalWidth
619 if decayString ==
"N -> charged hadrons":
625 Returns a dictionary of kinematically allowed decay channels
628 - decayString is a string describing the decay,
in the form
'N -> stuff1 ... stuffN'
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"})
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"})
652 allowedDecays.update({
"N -> eta nu_e":
"yes"})
653 allowedDecays.update({
"N -> eta nu_mu":
"yes"})
654 allowedDecays.update({
"N -> eta nu_tau":
"yes"})
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"})
668 allowedDecays.update({
"N -> eta1 nu_e":
"yes"})
669 allowedDecays.update({
"N -> eta1 nu_mu":
"yes"})
670 allowedDecays.update({
"N -> eta1 nu_tau":
"yes"})
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"})
695 if decay
not in allowedDecays:
696 allowedDecays.update({decay:
"no"})
702 HNL physics according to the nuMSM
705 def __init__(self, mass, couplings, debug: bool =
False) ->
None:
707 Initialize with mass
and couplings of the HNL
711 couplings (list of [U2e, U2mu, U2tau])
713 HNLbranchings.__init__(self, mass, couplings, debug)
717 Compute the HNL lifetime
720 - system: choose between default (i.e. SI, result in s)
or FairShip (result
in ns)
723 if system ==
"FairShip":
def computeNLifetime(self, str system="SI")
None __init__(self, mass, couplings, bool debug=False)
float Width_neutral_mesons(self)
dict[str, str] allowedChannels(self)
float|int Width_quarks_neutrino(self)
def Width_l_u_d(self, int alpha, int beta, int gamma)
def Width_nu_f_fbar(self, int alpha, int beta)
None __init__(self, mass, couplings, bool debug=False)
float|int sqrt_lambda(self, float a, b, c)
def integral(self, x1, x2, int x3)
def Width_H_l(self, str H, int alpha)
def findBranchingRatio(self, decayString)
def Width_l1_l2_nu2(self, int alpha, int beta)
float Width_charged_leptons(self)
float Width_charged_mesons(self)
def Width_H0_nu(self, str H, int alpha)
def Integrand(self, xx, xi)
float|int Width_quarks_lepton(self)