FairShip
Loading...
Searching...
No Matches
rpvsusy.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
4r"""
5# ==================================================================
6# Python module
7#
8# This module provides methods to compute the lifetime and
9# branching ratio of SUSY RPV neutralino given its mass and couplings as
10# input parameters.
11#
12# Created: 30/04/2016 Konstantinos A. Petridis (konstantinos.petridis@cern.ch)
13#
14# Sample usage:
15# ipython -i rpvsusy.py
16# In [1]: b = RPVSYSY(1.,[1, 1],1e3,1,True)
17# HNLbranchings instance initialized with couplings:
18# \lambda_{production} = 1GeV
19# \lambda_{decay} = 1GeV
20# universal sfermion mass = 1e3GeV
21#
22# benchmark scenario:
23# 1 (values between 1 and 5)
24# and mass:
25# m = 1.0 GeV
26# In [2]: b.computeNLifetime()
27# Out[2]: 0.0219634078804
28# In [3]: b.findBranchingRatio('N -> K+ mu-')
29# Out[3]: 0.11826749348890987
30#
31# ==================================================================
32"""
33
34import math
35import re
36
37import ROOT
38import shipunit as u
39
40# Load PDG database
41pdg = ROOT.TDatabasePDG.Instance()
42
43
44def PDGname(particle):
45 """
46 Trim particle name for use with the PDG database
47 """
48 if "down" in particle:
49 return "d"
50 if "up" in particle:
51 return "u"
52 if "strange" in particle:
53 return "s"
54 if "charm" in particle:
55 return "c"
56 if "bottom" in particle:
57 return "b"
58 if "beauty" in particle:
59 return "b"
60 if "top" in particle:
61 return "t"
62 if "1" in particle:
63 particle = particle.replace("1", "'")
64 if not (
65 ("-" in particle)
66 or ("+" in particle)
67 or ("0" in particle)
68 or ("nu_" in particle)
69 or ("eta" in particle)
70 or ("phi" in particle)
71 ) and (particle not in ["d", "u", "s", "c", "b", "t"]):
72 particle += "+"
73 return particle
74
75
76def PDGcode(particle: str) -> int:
77 """
78 Read particle ID from PDG database
79 """
80 particle = PDGname(particle)
81 tPart = pdg.GetParticle(particle)
82 return int(tPart.PdgCode())
83
84
85def mass(particle):
86 """
87 Read particle mass from PDG database
88 """
89 particle = PDGname(particle)
90 tPart = pdg.GetParticle(particle)
91 return tPart.Mass()
92
93
94def width(particle):
95 """
96 Read particle width from PDG database
97 """
98 particle = PDGname(particle)
99 tPart = pdg.GetParticle(particle)
100 return tPart.Width()
101
102
103def lifetime(particle):
104 """
105 Read particle lifetime from PDG database
106 """
107 particle = PDGname(particle)
108 tPart = pdg.GetParticle(particle)
109 if particle == "D0" or particle == "D0bar":
110 return 4.1e-13
111 elif particle == "D+" or particle == "D-":
112 return 1.0e-12
113 elif particle == "D_s+" or particle == "D_s-":
114 return 5.0e-13
115 elif particle == "B0" or particle == "B0bar":
116 return 1.5e-12
117 elif particle == "B+" or particle == "B-":
118 return 1.6e-12
119
120 return tPart.Lifetime()
121
122
124 """
125 Store some constants useful for HNL physics
126 """
127
128 def __init__(self) -> None:
130 "K+": 0.156 * u.GeV,
131 "K-": 0.156 * u.GeV,
132 "K_L0": 0.156 / math.sqrt(2) * u.GeV,
133 "K_S0": 0.156 / math.sqrt(2) * u.GeV,
134 "K*0": 0.230 * u.GeV,
135 "K*0_bar": 0.230 * u.GeV,
136 "K*+": 0.230 * u.GeV,
137 "K*-": 0.230 * u.GeV,
138 "phi": 0.230 * u.GeV,
139 "eta": -0.142 * u.GeV,
140 "eta'": 0.038 * u.GeV,
141 "D+": 0.205 * u.GeV,
142 "D*+": 0.205 * u.GeV,
143 "D-": 0.205 * u.GeV,
144 "D*-": 0.205 * u.GeV,
145 "D_s+": 0.259 * u.GeV,
146 "D*_s+": 0.259 * u.GeV,
147 "D_s-": 0.259 * u.GeV,
148 "D*_s-": 0.259 * u.GeV,
149 "B+": 0.191 * u.GeV,
150 "B-": 0.191 * u.GeV,
151 "B0": 0.191 * u.GeV,
152 "B_s0": 0.228 * u.GeV,
153 "B0_bar": 0.191 * u.GeV,
154 "B_s0_bar": 0.228 * u.GeV,
155 }
156
157 self.GF = 1.166379e-05 / (u.GeV * u.GeV) # Fermi's constant (GeV^-2)
158 self.MW = 80.385 * u.GeV
159 self.gW2 = self.GF / math.sqrt(2) * 8 * self.MW * self.MW # SU(2)L gauge coupling squared
160 self.s2thetaw = 0.23126 # square sine of the Weinberg angle
161 self.t2thetaw = self.s2thetaw / (1 - self.s2thetaw) # square tan of the Weinberg angle
162 self.heV = 6.58211928 * pow(10.0, -16) # no units or it messes up!!
163 self.hGeV = self.heV * pow(10.0, -9) # no units or it messes up!!
164 # defined in Eq (30)--(32) of [1511.07436], but without
165 # the coupling over sfermion mass, that will come later
166 self.GST2 = {
167 "slepton": self.gW2 * self.t2thetaw * 9.0 / 8.0,
168 "sneutrino": self.gW2 * self.t2thetaw * 9.0 / 8.0,
169 "tlepton": self.gW2 * self.t2thetaw * 1.0 / 32.0,
170 "tneutrino": self.gW2 * self.t2thetaw * 1.0 / 32.0,
171 }
172
173
174# Load some useful constants
176
177
179 """
180 Lifetime and total and partial decay widths of an RPV neutralino
181 """
182
183 def __init__(self, mass, couplings, sfmass, benchmark, debug: bool = False) -> None:
184 r"""
185 Initialize with mass and couplings of the RPV neutralino
186
187 Inputs:
188 mass (GeV)
189 which benchmark scenario (1,2,3,4,5)
190 couplings (list of [\lambda production, \lambda decay])
191 sfermion mass to normalise the couplings by (GeV)
192 """
193 self.MN = mass * u.GeV
194 self.U = couplings
195 self.U2 = [ui * ui for ui in self.U]
196 self.sfmass = sfmass
197 self.bench = benchmark
198 self.decays = {
199 1: ["N -> K+ mu-", "N -> K*+ mu-", "N -> K*0 nu_mu", "N -> K_L0 nu_mu", "N -> K_S0 nu_mu"],
200 2: [
201 "N -> K+ mu-",
202 "N -> K*+ mu-",
203 "N -> K*0 nu_mu",
204 "N -> K_L0 nu_mu",
205 "N -> K_S0 nu_mu",
206 "N -> eta nu_mu",
207 "N -> eta' nu_mu",
208 "N -> phi nu_mu",
209 ],
210 3: ["N -> K+ mu-", "N -> K*+ mu-", "N -> K*0 nu_mu", "N -> K_L0 nu_mu", "N -> K_S0 nu_mu"],
211 4: ["N -> D+ mu-", "N -> D*+ mu-", "N -> K*0 nu_mu", "N -> K_L0 nu_mu", "N -> K_S0 nu_mu"],
212 5: ["N -> K+ tau-", "N -> K*+ tau-", "N -> K*0 nu_tau", "N -> K_L0 nu_tau", "N -> K_S0 nu_tau"],
213 }
214
215 self.prods = {
216 1: ["D+ -> N mu+"],
217 2: ["D_s+ -> N mu+"],
218 3: ["B0 -> N nu_mu"],
219 4: ["B0 -> N nu_mu"],
220 5: ["B0 -> N nu_tau", "B+ -> N tau+"],
221 }
222
223 if debug:
224 print("RPVSUSYbranchings instance initialized with couplings:")
225 print("\t benchmark scenario = %d" % self.bench)
226 print("\t decay coupling = %s" % self.U[0])
227 print("\t production coupling = %s" % self.U[1])
228 print("\t sfermion mass = %s" % self.sfmass)
229 print("\t total prod coupling = %s" % (self.U[0] / self.sfmass**2))
230 print("\t total decay coupling = %s" % (self.U[1] / self.sfmass**2))
231 print("and mass:")
232 print("\tm = %s GeV" % (self.MN))
233
234 def Get_Prod_Modes(self) -> list[str]:
235 return self.prods[self.bench]
236
237 def Get_Dec_Modes(self) -> list[str]:
238 return self.decays[self.bench]
239
240 def AddChannelsToPythia(self, P8Gen, verbose: bool = True) -> None:
241 decays_tmp = self.decays[self.bench]
242 for decay in decays_tmp:
243 decay_cpy = decay
244 decay_cpy = decay_cpy.replace("N -> ", "")
245 particles = decay_cpy.split()
246 codes = [PDGcode(p) for p in particles]
247 print(decay)
248 bf = self.findDecayBranchingRatio(decay)
249 if (
250 any("K+" in s for s in particles)
251 or any("K-" in s for s in particles)
252 or any("K*+" in s for s in particles)
253 or any("K*-" in s for s in particles)
254 or any("K*0" in s for s in particles)
255 or any("K*0_bar" in s for s in particles)
256 ):
257 bf = bf / 2.0
258 P8Gen.SetParameters(
259 "9900015:addChannel = 1 {:.12} 0 {}".format(bf, " ".join(str(code) for code in codes))
260 )
261 P8Gen.SetParameters(
262 "9900015:addChannel = 1 {:.12} 0 {}".format(bf, " ".join(str(-code) for code in codes))
263 )
264 else:
265 P8Gen.SetParameters(
266 "9900015:addChannel = 1 {:.12} 0 {}".format(bf, " ".join(str(code) for code in codes))
267 )
268 if verbose:
269 print("debug readdecay table", particles, codes, bf)
270
271 def Width_H_L(self, H, L):
272 """
273 Returns the RPV neutralino decay width into neutral meson and lepton
274
275 Inputs:
276 - H is a string (name of the meson)
277 - L is a string (name of the lepton)
278 """
279 if (mass(H) + mass(L)) > self.MN:
280 return 0.0
281
282 phsp = math.sqrt(
283 self.MN**4
284 + mass(H) ** 4
285 + mass(L) ** 4
286 - 2 * self.MN**2 * mass(H) ** 2
287 - 2 * self.MN**2 * mass(L) ** 2
288 - 2 * mass(H) ** 2 * mass(L) ** 2
289 )
290 tmp_width = 0
291 if "nu_mu" in L or "nu_e" in L or "nu_tau" in L:
292 tmp_width = (
293 phsp
294 / (self.sfmass**4 * 128 * u.pi * self.MN**3)
295 * c.GST2["sneutrino"]
296 * c.decayConstant[H] ** 2
297 * (self.MN**2 + mass(L) ** 2 - mass(H) ** 2)
298 )
299 else:
300 tmp_width = (
301 phsp
302 / (self.sfmass**4 * 128 * u.pi * self.MN**3)
303 * c.GST2["slepton"]
304 * c.decayConstant[H] ** 2
305 * (self.MN**2 + mass(L) ** 2 - mass(H) ** 2)
306 )
307
308 if "K*" in H or "D*" in H or "phi" in H:
309 if "nu_mu" in L or "nu_e" in L or "nu_tau" in L:
310 tmp_width = (
311 phsp
312 / (self.sfmass**4 * 2 * u.pi * self.MN**3)
313 * c.GST2["tneutrino"]
314 * c.decayConstant[H] ** 2
315 * (2 * (self.MN**2 - mass(L) ** 2) ** 2 - mass(H) ** 2 * (mass(H) ** 2 + self.MN**2 + mass(L) ** 2))
316 )
317 else:
318 tmp_width = (
319 phsp
320 / (self.sfmass**4 * 2 * u.pi * self.MN**3)
321 * c.GST2["tlepton"]
322 * c.decayConstant[H] ** 2
323 * (2 * (self.MN**2 - mass(L) ** 2) ** 2 - mass(H) ** 2 * (mass(H) ** 2 + self.MN**2 + mass(L) ** 2))
324 )
325 width = self.U2[1] * tmp_width
326 # contributions both from decay and production couplings
327 if self.bench == 1 and ("K_" in H or "K*" in H):
328 width = (self.U2[0] + self.U2[1]) * tmp_width
329 # contributions only from production coupling
330 if self.bench == 2 and ("eta" in H or "phi" in H):
331 width = self.U2[0] * tmp_width
332
333 # Majorana case (charge conjugate channels)
334 width = 2.0 * width
335 return width
336
337 def Width_N_L(self, H, L):
338 """
339 Returns the Meson decay width to RPV neutralino and lepton
340 Inputs:
341 - H is a string (name of the meson)
342 - L is a string (name of the lepton)
343 """
344
345 if mass(H) < (self.MN + mass(L)):
346 return 0.0
347 phsp = math.sqrt(
348 self.MN**4
349 + mass(H) ** 4
350 + mass(L) ** 4
351 - 2 * self.MN**2 * mass(H) ** 2
352 - 2 * self.MN**2 * mass(L) ** 2
353 - 2 * mass(H) ** 2 * mass(L) ** 2
354 )
355
356 tmp_width = 0
357 if "nu_mu" in L or "nu_e" in L or "nu_tau" in L:
358 tmp_width = (
359 phsp
360 / (self.sfmass**4 * 64 * u.pi * mass(H) ** 3)
361 * c.GST2["sneutrino"]
362 * c.decayConstant[H] ** 2
363 * (mass(H) ** 2 - self.MN**2 - mass(L) ** 2)
364 )
365 else:
366 tmp_width = (
367 phsp
368 / (self.sfmass**4 * 64 * u.pi * mass(H) ** 3)
369 * c.GST2["slepton"]
370 * c.decayConstant[H] ** 2
371 * (mass(H) ** 2 - self.MN**2 - mass(L) ** 2)
372 )
373
374 if "K*" in H or "D*" in H or "phi" in H:
375 if "nu_mu" in L or "nu_e" in L or "nu_tau" in L:
376 tmp_width = (
377 phsp
378 / (self.sfmass**4 * 3 * u.pi * mass(H) ** 3)
379 * c.GST2["tneutrino"]
380 * c.decayConstant[H] ** 2
381 * (mass(H) ** 2 * (mass(H) ** 2 + self.MN**2 + mass(L) ** 2 - 2 * (self.MN**2 - mass(L) ** 2) ** 2))
382 )
383 else:
384 tmp_width = (
385 phsp
386 / (self.sfmass**4 * 3 * u.pi * mass(H) ** 3)
387 * c.GST2["tlepton"]
388 * c.decayConstant[H] ** 2
389 * (mass(H) ** 2 * (mass(H) ** 2 + self.MN**2 + mass(L) ** 2 - 2 * (self.MN**2 - mass(L) ** 2) ** 2))
390 )
391
392 width = self.U2[0] * tmp_width
393
394 return width
395
396 def NdecayWidth(self):
397 """
398 Returns the total SUSYRPV neutralino decay width
399 """
400 declist = self.decays[self.bench]
401 hadlist = [re.search(r"->\ (.+?)\ ", dec).group(1) for dec in declist]
402 leplist = [dlist[1].strip() for dlist in [re.findall(r"\ \w+", dec) for dec in declist]]
403 print(leplist, hadlist)
404 totalwidth = sum([self.Width_H_L(hadlist[i], leplist[i]) for i in range(0, len(hadlist))])
405 return totalwidth
406
407 def NprodWidth(self):
408 """
409 Returns the total SUSYRPV neutralino production width
410 """
411 declist = self.prods[self.bench]
412 hadlist = [re.search(r"(.+?)\ ->", dec).group(1) for dec in declist]
413 leplist = [dlist[1].strip() for dlist in [re.findall(r"\ \w+", dec) for dec in declist]]
414 totalwidth = sum([self.Width_N_L(hadlist[i], leplist[i]) for i in range(0, len(hadlist))])
415 return totalwidth
416
417 def findDecayBranchingRatio(self, decayString: str):
418 """
419 Returns the branching ratio of the selected SUSY RPV neutralino decay channel
420
421 Inputs:
422 - decayString is a string describing the decay, in the form 'N -> stuff1 ... stuffN'
423 """
424 had = re.search(r"->\ (.+?)\ ", decayString).group(1)
425 decaysplit = decayString.split(" ")
426 for split in decaysplit:
427 if split.find("mu") > -1 or split.find("e") > -1 or split.find("tau") > -1:
428 lep = split
429 if had == "pi+" or had == "pi-" or had == "pi0":
430 print(
431 "findBranchingRatio() ERROR: Pions in final "
432 "state have not been implemented, please choose "
433 "a different decay mode of out...\n"
434 )
435 print(self.decays)
436 return -999
437
438 corrdecstring = f"N -> {had} {lep}"
439 listdecs = self.decays[self.bench]
440 gooddec = False
441 print("findBranchingRation() INFO: You have chosen the decay: '", corrdecstring)
442 for dec in listdecs:
443 if corrdecstring in dec:
444 gooddec = True
445 if gooddec is False:
446 print("findBranchingRation() ERROR: Badly formulated decay string, please choose one of the following\n")
447 print(self.decays)
448 return -999
449
450 br = 0.0
451 totalwidth = self.NdecayWidth()
452 if totalwidth > 0.0:
453 br = self.Width_H_L(had, lep) / totalwidth
454 return br
455
456 def findProdBranchingRatio(self, decayString):
457 """
458 Returns the branching ratio of the selected SUSY RPV neutralino product channel
459
460 Inputs:
461 - decayString is a string describing the decay, in the form 'H -> N ... stuffN'
462 """
463 had = re.search(r"(.+?)\ ->", decayString).group(1)
464 decaysplit = decayString.split(" ")
465 for split in decaysplit:
466 if split.find("mu") > -1 or split.find("e") > -1 or split.find("tau") > -1:
467 lep = split
468 if had == "pi+" or had == "pi-" or had == "pi0":
469 print(
470 "findProdBranchingRatio() ERROR: Pions in final "
471 "state have not been implemented, please choose "
472 "a different decay mode of out...\n"
473 )
474 print(self.decays)
475 return -999
476
477 corrdecstring = f"{had} -> N {lep}"
478 listdecs = self.prods[self.bench]
479 gooddec = False
480 print("findProdBranchingRation() INFO: You have chosen the decay: '", corrdecstring)
481 for dec in listdecs:
482 if corrdecstring in dec:
483 gooddec = True
484 if gooddec is False:
485 print(
486 "findProdBranchingRation() ERROR: Badly formulated decay string, please choose one of the following\n"
487 )
488 print(self.decays)
489 return -999
490
491 br = self.Width_N_L(had, lep) / (self.Width_N_L(had, lep) + c.hGeV / lifetime(had))
492 return br
493
494
496 """
497 SUSY RPV neutralino physics according to the nuMSM
498 """
499
500 def __init__(self, mass, couplings, sfmass, benchmark, debug: bool = False) -> None:
501 r"""
502 Initialize with mass and couplings of the HNL
503
504 Inputs:
505 mass (GeV)
506 couplings (list of [\lambda_{production}^{2}, \lambda_{decay}^{2}])
507 mass of universal sfermion
508 which benchmark (integer between 1 and 5 inclusive)
509 """
510 RPVSUSYbranchings.__init__(self, mass, couplings, sfmass, benchmark, debug)
511
512 def computeNLifetime(self, system: str = "SI"):
513 """
514 Compute the RPV neutralino lifetime
515
516 Inputs:
517 - system: choose between default (i.e. SI, result in s) or FairShip (result in ns)
518 """
519 decwidth = self.NdecayWidth()
520 if decwidth == 0.0:
521 return 0.0
522 self.NLifetime = c.hGeV / decwidth
523 if system == "FairShip":
524 self.NLifetime *= 1.0e9
525 return self.NLifetime
def computeNLifetime(self, str system="SI")
Definition: rpvsusy.py:512
None __init__(self, mass, couplings, sfmass, benchmark, bool debug=False)
Definition: rpvsusy.py:500
def Width_H_L(self, H, L)
Definition: rpvsusy.py:271
list[str] Get_Dec_Modes(self)
Definition: rpvsusy.py:237
list[str] Get_Prod_Modes(self)
Definition: rpvsusy.py:234
None __init__(self, mass, couplings, sfmass, benchmark, bool debug=False)
Definition: rpvsusy.py:183
def findProdBranchingRatio(self, decayString)
Definition: rpvsusy.py:456
def findDecayBranchingRatio(self, str decayString)
Definition: rpvsusy.py:417
None AddChannelsToPythia(self, P8Gen, bool verbose=True)
Definition: rpvsusy.py:240
def Width_N_L(self, H, L)
Definition: rpvsusy.py:337
None __init__(self)
Definition: rpvsusy.py:128
def lifetime(particle)
Definition: rpvsusy.py:103
int PDGcode(str particle)
Definition: rpvsusy.py:76
def width(particle)
Definition: rpvsusy.py:94