FairShip
Loading...
Searching...
No Matches
pythia8_conf_utils Namespace Reference

Functions

None addHNLtoROOT (int pid=9900015, float m=1.0, float g=3.654203020370371e-21)
 
def getbr_rpvsusy (h, histoname, mass, coupling)
 
def getmaxsumbrrpvsusy (h, histograms, mass, couplings)
 
float gettotalbrrpvsusy (h, histograms, mass, couplings)
 
None make_particles_stable (P8gen, above_lifetime)
 
def parse_histograms (filepath)
 
def make_interpolators (filepath, str kind="linear")
 
def get_br (histograms, channel, mass, couplings)
 
None add_particles (P8gen, particles, data)
 
None add_channel (P8gen, ch, histograms, mass, couplings, scale_factor)
 
None add_tau_channel (P8gen, ch, histograms, mass, couplings, scale_factor)
 
None fill_missing_channels (P8gen, max_total_br, decay_chains, float epsilon=1e-6)
 
None add_dummy_channel (P8gen, particle, remainder)
 
int compute_max_total_br (decay_chains)
 
int compute_total_br (particle, decay_chains)
 
def get_top_level_particles (decay_chains)
 
None exit_if_zero_br (max_total_br, selection, mass, str particle="HNL")
 
None print_scale_factor (scaling_factor)
 

Function Documentation

◆ add_channel()

None pythia8_conf_utils.add_channel (   P8gen,
  ch,
  histograms,
  mass,
  couplings,
  scale_factor 
)

Definition at line 162 of file pythia8_conf_utils.py.

162def add_channel(P8gen, ch, histograms, mass, couplings, scale_factor) -> None:
163 "Add to PYTHIA a leptonic or semileptonic decay channel to HNL."
164 if "idlepton" in ch:
165 br = get_br(histograms, ch, mass, couplings)
166 if br <= 0: # Ignore kinematically closed channels
167 return
168 if "idhadron" in ch: # Semileptonic decay
169 P8gen.SetParameters(
170 "{}:addChannel 1 {:.17} 22 {} 9900015 {}".format(
171 ch["id"], br * scale_factor, ch["idlepton"], ch["idhadron"]
172 )
173 )
174 else: # Leptonic decay
175 P8gen.SetParameters(
176 "{}:addChannel 1 {:.17} 0 9900015 {}".format(
177 ch["id"], br * scale_factor, ch["idlepton"]
178 )
179 )
180 else: # Wrong decay
181 raise ValueError(f"Missing key 'idlepton' in channel {ch}")
182
183

◆ add_dummy_channel()

None pythia8_conf_utils.add_dummy_channel (   P8gen,
  particle,
  remainder 
)
Add a dummy channel to PYTHIA, with branching ratio equal to `remainder.`

The purpose of this function is to compensate for the absence of SM
channels, which are ignored when investigating rare processes. A dummy
decay channel is instead added to each particle in order to maintain the
correct ratios between the branching ratios of each particle to rare
processes. This is usually combined with a global reweighting of the
branching ratios.

In order to keep PYTHIA from complaining about charge conservation, a
suitable process is added which conserves electric charge.

All dummy channels can be identified by the presence of a photon among the
decay products.

Definition at line 226 of file pythia8_conf_utils.py.

226def add_dummy_channel(P8gen, particle, remainder) -> None:
227 """
228 Add a dummy channel to PYTHIA, with branching ratio equal to `remainder.`
229
230 The purpose of this function is to compensate for the absence of SM
231 channels, which are ignored when investigating rare processes. A dummy
232 decay channel is instead added to each particle in order to maintain the
233 correct ratios between the branching ratios of each particle to rare
234 processes. This is usually combined with a global reweighting of the
235 branching ratios.
236
237 In order to keep PYTHIA from complaining about charge conservation, a
238 suitable process is added which conserves electric charge.
239
240 All dummy channels can be identified by the presence of a photon among the
241 decay products.
242 """
243 pdg = P8gen.getPythiaInstance().particleData
244 charge = pdg.charge(particle)
245 if charge > 0:
246 P8gen.SetParameters(f"{particle}:addChannel 1 {remainder:.16} 0 22 -11")
247 elif charge < 0:
248 P8gen.SetParameters(f"{particle}:addChannel 1 {remainder:.16} 0 22 11")
249 else:
250 P8gen.SetParameters(f"{particle}:addChannel 1 {remainder:.16} 0 22 22")
251
252

◆ add_particles()

None pythia8_conf_utils.add_particles (   P8gen,
  particles,
  data 
)
Adds the corresponding particles to PYTHIA.

`particles` must be a list containing either the particles PDG IDs, or
their PYTHIA names. The commands needed to add the particles are queried
from `data`.

If the particle is not self-conjugate, the antiparticle is automatically
added by PYTHIA.

Definition at line 142 of file pythia8_conf_utils.py.

142def add_particles(P8gen, particles, data) -> None:
143 """
144 Adds the corresponding particles to PYTHIA.
145
146 `particles` must be a list containing either the particles PDG IDs, or
147 their PYTHIA names. The commands needed to add the particles are queried
148 from `data`.
149
150 If the particle is not self-conjugate, the antiparticle is automatically
151 added by PYTHIA.
152 """
153 for particle_id in particles:
154 # Find particle in database (None: particle not found)
155 particle = next((p for p in data["particles"] if particle_id in [p["id"], p["name"]]), None)
156 if particle is None:
157 raise ValueError(f"Could not find particle ID {particle_id} in particle database")
158 # Add the particle
159 P8gen.SetParameters(particle["cmd"])
160
161

◆ add_tau_channel()

None pythia8_conf_utils.add_tau_channel (   P8gen,
  ch,
  histograms,
  mass,
  couplings,
  scale_factor 
)

Definition at line 184 of file pythia8_conf_utils.py.

184def add_tau_channel(P8gen, ch, histograms, mass, couplings, scale_factor) -> None:
185 "Add to PYTHIA a tau decay channel to HNL."
186 if "idhadron" in ch:
187 br = get_br(histograms, ch, mass, couplings)
188 if br <= 0: # Ignore kinematically closed channels
189 return
190 if "idlepton" in ch: # 3-body leptonic decay
191 P8gen.SetParameters(
192 "{}:addChannel 1 {:.16} 1531 9900015 {} {}".format(
193 ch["id"], br * scale_factor, ch["idlepton"], ch["idhadron"]
194 )
195 )
196 else: # 2-body semileptonic decay
197 P8gen.SetParameters(
198 "{}:addChannel 1 {:.16} 1521 9900015 {}".format(
199 ch["id"], br * scale_factor, ch["idhadron"]
200 )
201 )
202 else:
203 raise ValueError(f"Missing key 'idhadron' in channel {ch}")
204
205

◆ addHNLtoROOT()

None pythia8_conf_utils.addHNLtoROOT ( int   pid = 9900015,
float   m = 1.0,
float   g = 3.654203020370371e-21 
)

Definition at line 12 of file pythia8_conf_utils.py.

12def addHNLtoROOT(pid: int = 9900015, m: float = 1.0, g: float = 3.654203020370371e-21) -> None:
13 pdg = ROOT.TDatabasePDG.Instance()
14 pdg.AddParticle("N2", "HNL", m, False, g, 0.0, "N2", pid)
15
16

◆ compute_max_total_br()

int pythia8_conf_utils.compute_max_total_br (   decay_chains)
This function computes the maximum total branching ratio for all decay chains.

In order to make the event generation as efficient as possible when
studying very rare processes, it is necessary to rescale the branching
ratios, while enforcing the invariant that any total branching ratio must
remain lower that unity.

This is accomplished by computing, for each particle, the total branching
ratio to processes of interest, and then dividing all branching ratios by
the highest of those.

Note: the decay chain length must be at most 2.

Definition at line 253 of file pythia8_conf_utils.py.

253def compute_max_total_br(decay_chains) -> int:
254 """
255 This function computes the maximum total branching ratio for all decay chains.
256
257 In order to make the event generation as efficient as possible when
258 studying very rare processes, it is necessary to rescale the branching
259 ratios, while enforcing the invariant that any total branching ratio must
260 remain lower that unity.
261
262 This is accomplished by computing, for each particle, the total branching
263 ratio to processes of interest, and then dividing all branching ratios by
264 the highest of those.
265
266 Note: the decay chain length must be at most 2.
267 """
268 # For each top-level charmed particle, sum BR over all its decay chains
269 top_level_particles = get_top_level_particles(decay_chains)
270 total_branching_ratios = [compute_total_br(particle, decay_chains) for particle in top_level_particles]
271 # Find the maximum total branching ratio
272 return max(total_branching_ratios)
273
274

◆ compute_total_br()

int pythia8_conf_utils.compute_total_br (   particle,
  decay_chains 
)
Returns the total branching ratio to HNLs for a given particle.

Definition at line 275 of file pythia8_conf_utils.py.

275def compute_total_br(particle, decay_chains) -> int:
276 """
277 Returns the total branching ratio to HNLs for a given particle.
278 """
279 return sum(np.prod(branching_ratios) for (top, branching_ratios) in decay_chains if top == particle)
280
281

◆ exit_if_zero_br()

None pythia8_conf_utils.exit_if_zero_br (   max_total_br,
  selection,
  mass,
str   particle = "HNL" 
)

Definition at line 289 of file pythia8_conf_utils.py.

289def exit_if_zero_br(max_total_br, selection, mass, particle: str = "HNL") -> None:
290 if max_total_br <= 0:
291 print(f"No phase space for {particle} from {selection} at this mass: {mass}. Quitting.")
292 sys.exit()
293
294

◆ fill_missing_channels()

None pythia8_conf_utils.fill_missing_channels (   P8gen,
  max_total_br,
  decay_chains,
float   epsilon = 1e-6 
)
Add dummy channels for correct rejection sampling.

Even after rescaling the branching ratios, they do not sum up to unity
for most particles since we are ignoring SM processes.

This function adds a "filler" channel for each particle, in order to
preserve the ratios between different branching ratios.

Definition at line 206 of file pythia8_conf_utils.py.

206def fill_missing_channels(P8gen, max_total_br, decay_chains, epsilon: float = 1e-6) -> None:
207 """
208 Add dummy channels for correct rejection sampling.
209
210 Even after rescaling the branching ratios, they do not sum up to unity
211 for most particles since we are ignoring SM processes.
212
213 This function adds a "filler" channel for each particle, in order to
214 preserve the ratios between different branching ratios.
215 """
216 top_level_particles = get_top_level_particles(decay_chains)
217 for particle in top_level_particles:
218 my_total_br = compute_total_br(particle, decay_chains)
219 remainder = 1 - my_total_br / max_total_br
220 assert remainder > -epsilon
221 assert remainder < 1 + epsilon
222 if remainder > epsilon:
223 add_dummy_channel(P8gen, particle, remainder)
224
225

◆ get_br()

def pythia8_conf_utils.get_br (   histograms,
  channel,
  mass,
  couplings 
)
Utility function used to reliably query the branching ratio for a given
channel at a given mass, taking into account the correct coupling.

Definition at line 131 of file pythia8_conf_utils.py.

131def get_br(histograms, channel, mass, couplings):
132 """
133 Utility function used to reliably query the branching ratio for a given
134 channel at a given mass, taking into account the correct coupling.
135 """
136 hist = histograms[channel["decay"]]
137 coupling = couplings[channel["coupling"]]
138 normalized_br = hist(mass)
139 return normalized_br * coupling
140
141

◆ get_top_level_particles()

def pythia8_conf_utils.get_top_level_particles (   decay_chains)
Returns the set of particles which are at the top of a decay chain.

Definition at line 282 of file pythia8_conf_utils.py.

282def get_top_level_particles(decay_chains):
283 """
284 Returns the set of particles which are at the top of a decay chain.
285 """
286 return {top for (top, branching_ratios) in decay_chains}
287
288

◆ getbr_rpvsusy()

def pythia8_conf_utils.getbr_rpvsusy (   h,
  histoname,
  mass,
  coupling 
)

Definition at line 17 of file pythia8_conf_utils.py.

17def getbr_rpvsusy(h, histoname, mass, coupling):
18 if histoname in h:
19 normalized_br = h[histoname](mass)
20 br = normalized_br * coupling
21 else:
22 br = 0
23 return br
24
25

◆ getmaxsumbrrpvsusy()

def pythia8_conf_utils.getmaxsumbrrpvsusy (   h,
  histograms,
  mass,
  couplings 
)

Definition at line 26 of file pythia8_conf_utils.py.

26def getmaxsumbrrpvsusy(h, histograms, mass, couplings):
27 # 0 MeV< mass < 3.200 GeV
28 maxsumbr = 0.0
29 sumbrs = {}
30 for histoname in histograms:
31 item = histoname.split("_")
32 item[len(item) - 1]
33 meson = item[0]
34 coupling = couplings[1]
35 try:
36 sumbrs[meson] += getbr_rpvsusy(h, histoname, mass, coupling)
37 except KeyError:
38 sumbrs[meson] = getbr_rpvsusy(h, histoname, mass, coupling)
39 print(list(sumbrs.values()))
40 maxsumbr = max(sumbrs.values())
41 return maxsumbr
42
43

◆ gettotalbrrpvsusy()

float pythia8_conf_utils.gettotalbrrpvsusy (   h,
  histograms,
  mass,
  couplings 
)

Definition at line 44 of file pythia8_conf_utils.py.

44def gettotalbrrpvsusy(h, histograms, mass, couplings) -> float:
45 totalbr = 0.0
46 for histoname in histograms:
47 histoname.split("_")
48 coupling = couplings[1]
49 totalbr += getbr_rpvsusy(h, histoname, mass, coupling)
50 return totalbr
51
52

◆ make_interpolators()

def pythia8_conf_utils.make_interpolators (   filepath,
str   kind = "linear" 
)
This function reads a file containing branching ratio histograms, and
returns a dictionary of interpolators of the branching ratios, indexed by
the decay string.

Definition at line 116 of file pythia8_conf_utils.py.

116def make_interpolators(filepath, kind: str = "linear"):
117 """
118 This function reads a file containing branching ratio histograms, and
119 returns a dictionary of interpolators of the branching ratios, indexed by
120 the decay string.
121 """
122 histogram_data = parse_histograms(filepath)
123 histograms = {}
124 for hist_string, (masses, br) in histogram_data.items():
125 histograms[hist_string] = scipy.interpolate.interp1d(
126 masses, br, kind=kind, bounds_error=False, fill_value=0, assume_sorted=True
127 )
128 return histograms
129
130

◆ make_particles_stable()

None pythia8_conf_utils.make_particles_stable (   P8gen,
  above_lifetime 
)
Make the particles with a lifetime above the specified one stable, to allow
them to decay in Geant4 instead.

Definition at line 53 of file pythia8_conf_utils.py.

53def make_particles_stable(P8gen, above_lifetime) -> None:
54 # FIXME: find time unit and add it to the docstring
55 """
56 Make the particles with a lifetime above the specified one stable, to allow
57 them to decay in Geant4 instead.
58 """
59 p8 = P8gen.getPythiaInstance()
60 n = 1
61 while n != 0:
62 n = p8.particleData.nextId(n)
63 p = p8.particleData.particleDataEntryPtr(n)
64 if p.tau0() > above_lifetime:
65 command = f"{n}:mayDecay = false"
66 p8.readString(command)
67 print(f"Pythia8 configuration: Made {p.name()} stable for Pythia, should decay in Geant4")
68
69

◆ parse_histograms()

def pythia8_conf_utils.parse_histograms (   filepath)
This function parses a file containing histograms of branching ratios.

It places them in a dictionary indexed by the decay string (e.g. 'd_K0_e'),
as a pair ([masses...], [branching ratios...]), where the mass is expressed
in GeV.

Definition at line 70 of file pythia8_conf_utils.py.

70def parse_histograms(filepath):
71 """
72 This function parses a file containing histograms of branching ratios.
73
74 It places them in a dictionary indexed by the decay string (e.g. 'd_K0_e'),
75 as a pair ([masses...], [branching ratios...]), where the mass is expressed
76 in GeV.
77 """
78 with open(filepath) as f:
79 lines = f.readlines()
80 # Define regular expressions matching (sub-)headers and data lines
81 th1f_exp = re.compile(r"^TH1F\|.+")
82 header_exp = re.compile(r"^TH1F\|(.+?)\|B(?:R|F)/U2(.+?)\|.+? mass \‍(GeV\‍)\|?")
83 subheader_exp = re.compile(r"^\s*?(\d+?),\s*(\d+?\.\d+?),\s*(\d+\.\d+)\s*$")
84 data_exp = re.compile(r"^\s*(\d+?)\s*,\s*(\d+\.\d+)\s*$")
85 # Locate beginning of each histogram
86 header_line_idx = [i for i in range(len(lines)) if th1f_exp.match(lines[i]) is not None]
87 # Iterate over histograms
88 histograms = {}
89 for offset in header_line_idx:
90 # Parse header
91 mh = header_exp.match(lines[offset])
92 if mh is None or len(mh.groups()) != 2:
93 raise ValueError(f"Malformed header encountered: {lines[offset]}")
94 decay_code = mh.group(1)
95 # Parse sub-header (min/max mass and number of points)
96 ms = subheader_exp.match(lines[offset + 1])
97 if ms is None or len(ms.groups()) != 3:
98 raise ValueError(f"Malformed sub-header encountered: {lines[offset + 1]}")
99 npoints = int(ms.group(1))
100 min_mass = float(ms.group(2))
101 max_mass = float(ms.group(3))
102 masses = np.linspace(min_mass, max_mass, npoints, endpoint=False)
103 branching_ratios = np.zeros(npoints)
104 # Now read the data lines (skipping the two header lines)
105 for line in lines[offset + 2 : offset + npoints + 1]:
106 md = data_exp.match(line)
107 if md is None or len(md.groups()) != 2:
108 raise ValueError(f"Malformed data row encountered: {line}")
109 idx = int(md.group(1))
110 br = float(md.group(2))
111 branching_ratios[idx] = br
112 histograms[decay_code] = (masses, branching_ratios)
113 return histograms
114
115
int open(const char *, int)
Opens a file descriptor.

◆ print_scale_factor()

None pythia8_conf_utils.print_scale_factor (   scaling_factor)

Definition at line 295 of file pythia8_conf_utils.py.

295def print_scale_factor(scaling_factor) -> None:
296 "Prints the scale factor used to make event generation more efficient."
297 print(f"One simulated event per {scaling_factor:.4g} meson decays")