FairShip
Loading...
Searching...
No Matches
makeCascade.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
4import argparse
5import copy
6import os
7import random
8import sys
9import time
10from array import array
11
12import ROOT
13import rootUtils as ut
14
15ROOT.gROOT.LoadMacro("$VMCWORKDIR/gconfig/basiclibs.C")
16ROOT.basiclibs()
17timer = ROOT.TStopwatch()
18timer.Start()
19
20ap = argparse.ArgumentParser(description="Run SHiP makeCascade: generate ccbar or bbar")
21ap.add_argument(
22 "-s",
23 "--seed",
24 type=int,
25 default=int(time.time() * 100000000 % 900000000),
26 help="Random number seed, integer. If not given, current time will be used",
27)
28ap.add_argument(
29 "-t",
30 "--Fntuple",
31 default="",
32 help="Name of ntuple output file, default: Cascade{nevgen/1000}k-parp16-MSTP82-1-MSEL{mselcb}-ntuple.root",
33)
34ap.add_argument("-n", "--nevgen", type=int, default=100000, help="Number of events to produce, default 100000")
35ap.add_argument("-E", "--pbeamh", type=float, default=400.0, help="Energy of beam in GeV, default 400 GeV")
36ap.add_argument(
37 "-m", "--mselcb", type=int, default=4, help="4 (5): charm (beauty) production, default charm", choices=[4, 5]
38)
39ap.add_argument(
40 "-P",
41 "--storePrimaries",
42 action=argparse.BooleanOptionalAction,
43 default=False,
44 help="If this argument is used, store all particles produced together with charm.",
45)
46ap.add_argument(
47 "--target_composition",
48 default="W",
49 help="Target composition (to determine the ratio of protons in the material). Default is Tungsten (W). Only other choice is Molybdenum (Mo)",
50 choices=["W", "Mo"],
51)
52ap.add_argument(
53 "--pythia_tune",
54 default="PoorE791",
55 help="Choices of Pythia tune are PoorE791 (default, settings with default Pythia6 pdf, based on getting <pt> at 500 GeV pi-) or LHCb (settings by LHCb for Pythia 6.427)",
56 choices=["PoorE791", "LHCb"],
57)
58# some parameters for generating the chi (sigma(signal)/sigma(total) as a function of momentum
59ap.add_argument("--nev", type=int, default=5000, help="Events / momentum")
60ap.add_argument("--nrpoints", type=int, default=20, help="Number of momentum points taken to calculate sig/sigtot")
61
62args = ap.parse_args()
63if args.Fntuple == "":
64 args.Fntuple = f"Cascade{int(args.nevgen / 1000)}k-parp16-MSTP82-1-MSEL{args.mselcb}-ntuple.root"
65
66print(f"Generate {args.nevgen} p.o.t. with msel = {args.mselcb} proton beam {args.pbeamh}GeV")
67print(f"Output ntuples written to: {args.Fntuple}")
68
69# cascade beam particles, anti-particles are generated automatically if they exist.
70idbeam = [2212, 211, 2112, 321, 130, 310]
71target = ["p+", "n0"]
72print(f"Chi generation with {args.nev} events/point, nr points={args.nrpoints}")
73print(f"Cascade beam particle: {idbeam}")
74
75# fracp is the fraction of protons in nucleus, used to average chi on p and n target in Pythia.
76if args.target_composition == "W": # target is Tungsten, fracp is 74/(184)
77 fracp = 0.40
78else: # target would then be Molybdenum, fracp is 42/98
79 fracp = 0.43
80
81print(f"Target particles: {target}, fraction of protons in {args.target_composition}={fracp}")
82
83# lower/upper momentum limit for beam, depends on msel..
84# signal particles wanted (and their antis), which could decay semi-leptonically.
85if args.mselcb == 4:
86 pbeaml = 34.0
87 idsig = [411, 421, 431, 4122, 4132, 4232, 4332, 4412, 4414, 4422, 4424, 4432, 4434, 4444]
88elif args.mselcb == 5:
89 pbeaml = 130.0
90 idsig = [
91 511,
92 521,
93 531,
94 541,
95 5122,
96 5132,
97 5142,
98 5232,
99 5242,
100 5332,
101 5342,
102 5412,
103 5414,
104 5422,
105 5424,
106 5432,
107 5434,
108 5442,
109 5444,
110 5512,
111 5514,
112 5522,
113 5524,
114 5532,
115 5534,
116 5542,
117 5544,
118 5554,
119 ]
120else:
121 print(f"Error: msel is unknown: {args.mselcb}, STOP program")
122 sys.exit("ERROR on input, exit")
123
124PDG = ROOT.TDatabasePDG.Instance()
125myPythia = ROOT.TPythia6()
126tp = ROOT.tPythia6Generator()
127
128# Pythia6 can only accept names below in pyinit, hence reset PDG table:
129PDG.GetParticle(2212).SetName("p+")
130PDG.GetParticle(-2212).SetName("pbar-")
131PDG.GetParticle(2112).SetName("n0")
132PDG.GetParticle(-2112).SetName("nbar0")
133PDG.GetParticle(130).SetName("KL0")
134PDG.GetParticle(310).SetName("KS0")
135# lower lowest sqrt(s) allowed for generating events
136myPythia.SetPARP(2, 2.0)
137
138
139def PoorE791_tune(P6) -> None:
140 # settings with default Pythia6 pdf, based on getting <pt> at 500 GeV pi-
141 # same as that of E791: http://arxiv.org/pdf/hep-ex/9906034.pdf
142 print(" ")
143 print("********** PoorE791_tune **********")
144 # change pt of D's to mimic E791 data.
145 P6.SetPARP(91, 1.6)
146 print(f"PARP(91)={P6.GetPARP(91)}")
147 # what PDFs etc.. are being used:
148 print("MSTP PDF info, i.e. (51) and (52)=", P6.GetMSTP(51), P6.GetMSTP(52))
149 # set multiple interactions equal to Fortran version, i.e.=1, default=4, and adapt parp(89) accordingly
150 P6.SetMSTP(82, 1)
151 P6.SetPARP(89, 1000.0)
152 print(
153 "And corresponding multiple parton stuff, i.e. MSTP(82),PARP(81,89,90)=",
154 P6.GetMSTP(82),
155 P6.GetPARP(81),
156 P6.GetPARP(89),
157 P6.GetPARP(90),
158 )
159 print("********** PoorE791_tune **********")
160 print(" ")
161
162
163def LHCb_tune(P6) -> None:
164 # settings by LHCb for Pythia 6.427
165 # https://twiki.cern.ch/twiki/bin/view/LHCb/SettingsSim08
166 print(" ")
167 print("********** LHCb_tune **********")
168 # P6.SetCKIN(41,3.0)
169 P6.SetMSTP(2, 2)
170 P6.SetMSTP(33, 3)
171 # P6.SetMSTP(128, 2) # store or not store
172 P6.SetMSTP(81, 21)
173 P6.SetMSTP(82, 3)
174 P6.SetMSTP(52, 2)
175 P6.SetMSTP(51, 10042) # (ie CTEQ6 LO fit, with LOrder alpha_S PDF from LHAPDF)
176 P6.SetMSTP(142, 0) # do not weigh events
177 P6.SetPARP(67, 1.0)
178 P6.SetPARP(82, 4.28)
179 P6.SetPARP(89, 14000)
180 P6.SetPARP(90, 0.238)
181 P6.SetPARP(85, 0.33)
182 P6.SetPARP(86, 0.66)
183 P6.SetPARP(91, 1.0)
184 P6.SetPARP(149, 0.02)
185 P6.SetPARP(150, 0.085)
186 P6.SetPARJ(11, 0.4)
187 P6.SetPARJ(12, 0.4)
188 P6.SetPARJ(13, 0.769)
189 P6.SetPARJ(14, 0.09)
190 P6.SetPARJ(15, 0.018)
191 P6.SetPARJ(16, 0.0815)
192 P6.SetPARJ(17, 0.0815)
193 P6.SetMSTJ(26, 0)
194 P6.SetPARJ(33, 0.4)
195 print("********** LHCb_tune **********")
196 print(" ")
197
198
199def fillp1(hist) -> None:
200 # scan filled bins in hist, and fill intermediate bins with linear interpolation
201 nb = hist.GetNbinsX()
202 i1 = hist.FindBin(pbeaml, 0.0, 0.0)
203 y1 = hist.GetBinContent(i1)
204 p1 = hist.GetBinCenter(i1)
205 for i in range(i1 + 1, nb + 1):
206 if hist.GetBinContent(i) > 0.0:
207 y2 = hist.GetBinContent(i)
208 p2 = hist.GetBinCenter(i)
209 tg = (y2 - y1) / (p2 - p1)
210 for ib in range(i1 + 1, i):
211 px = hist.GetBinCenter(ib)
212 yx = (px - p1) * tg + y1
213 hist.Fill(px, yx)
214 i1 = i
215 y1 = y2
216 p1 = p2
217
218
219if args.pythia_tune == "PoorE791":
220 PoorE791_tune(myPythia)
221 myPythia.OpenFortranFile(11, os.devnull) # Pythia output to dummy (11) file (to screen use 6)
222 myPythia.SetMSTU(11, 11)
223else:
224 LHCb_tune(myPythia)
225 myPythia.OpenFortranFile(6, os.devnull) # avoid any printing to the screen, only when LHAPDF is used, in LHCb tune
226
227# start with different random number for each run...
228print(f"Setting random number seed = {args.seed}")
229myPythia.SetMRPY(1, args.seed)
230
231# histogram helper
232h = {}
233
234# initialise phase, determine chi=sigma(signal)/sigma(total) for many beam momenta.
235# loop over beam particles
236id = 0
237nb = 400
238t0 = time.time()
239idhist = len(idbeam) * 4 * [0]
240print("Get chi vs momentum for all beam+target particles")
241for idp in range(0, len(idbeam)):
242 for idpm in [-1, 1]: # particle or anti-particle
243 idw = idbeam[idp] * idpm
244 if PDG.GetParticle(idw) is not None: # if particle exists, book hists etc.
245 name = PDG.GetParticle(idw).GetName()
246 id = id + 1
247 for idnp in range(2):
248 idb = id * 10 + idnp * 4
249 ut.bookHist(h, str(idb + 1), f"sigma vs p, {name} -> {target[idnp]}", nb, 0.5, args.pbeamh + 0.5)
250 ut.bookHist(h, str(idb + 2), f"sigma-tot vs p, {name} -> {target[idnp]}", nb, 0.5, args.pbeamh + 0.5)
251 ut.bookHist(h, str(idb + 3), f"chi vs p, {name}-> {target[idnp]}", nb, 0.5, args.pbeamh + 0.5)
252 ut.bookHist(h, str(idb + 4), f"Prob(normalised), {name} -> {target[idnp]}", nb, 0.5, args.pbeamh + 0.5)
253 # keep track of histogram<->Particle id relation.
254 idhist[id] = idw
255 for idpn in range(2): # target is proton or neutron
256 idadd = idpn * 4
257 print(f"{name}, {target[idpn]}, for chi, seconds: {time.time() - t0}")
258 for ipbeam in range(args.nrpoints): # loop over beam momentum
259 pbw = ipbeam * (args.pbeamh - pbeaml) / (args.nrpoints - 1) + pbeaml
260 # convert to center of a bin
261 ibin = h[str(id * 10 + 1 + idadd)].FindBin(pbw, 0.0, 0.0)
262 pbw = h[str(id * 10 + 1 + idadd)].GetBinCenter(ibin)
263 # new particle/momentum, init again, first signal run.
264 myPythia.SetMSEL(args.mselcb) # set forced ccbar or bbbar generation
265 myPythia.Initialize("FIXT", name, target[idpn], pbw)
266 for iev in range(args.nev):
267 myPythia.GenerateEvent()
268 # signal run finished, get cross-section
269 h[str(id * 10 + 1 + idadd)].Fill(pbw, tp.getPyint5_XSEC(2, 0))
270 myPythia.SetMSEL(2) # now total cross-section, i.e. msel=2
271 myPythia.Initialize("FIXT", name, target[idpn], pbw)
272 for iev in range(args.nev // 10):
273 # if iev%100==0: print 'generated mbias events',iev,' seconds:',time.time()-t0
274 myPythia.GenerateEvent()
275 # get cross-section
276 h[str(id * 10 + 2 + idadd)].Fill(pbw, tp.getPyint5_XSEC(2, 0))
277 # for this beam particle, do arithmetic to get chi.
278 h[str(id * 10 + 3 + idadd)].Divide(h[str(id * 10 + 1 + idadd)], h[str(id * 10 + 2 + idadd)], 1.0, 1.0)
279 # fill with linear function between evaluated momentum points.
280 fillp1(h[str(id * 10 + 3 + idadd)])
281
282# collected all, now re-scale to make max chi at 400 Gev==1.
283chimx = 0.0
284for i in range(1, id + 1):
285 for idpn in range(2):
286 idw = i * 10 + idpn * 4 + 3
287 ibh = h[str(idw)].FindBin(args.pbeamh)
288 print(
289 f"beam id: {i}, {idw}, {idhist[i]}, momentum: {args.pbeamh},chi: {h[str(idw)].GetBinContent(ibh)}, chimx: {chimx}"
290 )
291 if h[str(idw)].GetBinContent(ibh) > chimx:
292 chimx = h[str(idw)].GetBinContent(ibh)
293chimx = 1.0 / chimx
294for i in range(1, id + 1):
295 for idpn in range(2):
296 idw = i * 10 + idpn * 4 + 3
297 h[str(idw + 1)].Add(h[str(idw)], h[str(idw)], chimx, 0.0)
298
299# switch output printing OFF for generation phase.
300# Generate ccbar (or bbbar) pairs according to probability in hists i*10+8.
301# some check histos
302ut.bookHist(h, str(1), "E of signals", 100, 0.0, 400.0)
303ut.bookHist(h, str(2), "nr signal per cascade depth", 50, 0.5, 50.5)
304ut.bookHist(h, str(3), "D0 pt**2", 40, 0.0, 4.0)
305ut.bookHist(h, str(4), "D0 pt**2", 100, 0.0, 18.0)
306ut.bookHist(h, str(5), "D0 pt", 100, 0.0, 10.0)
307ut.bookHist(h, str(6), "D0 XF", 100, -1.0, 1.0)
308
309ftup = ROOT.TFile.Open(args.Fntuple, "RECREATE")
310Ntup = ROOT.TNtuple(
311 "pythia6",
312 "pythia6 heavy flavour",
313 "id:px:py:pz:E:M:mid:mpx:mpy:mpz:mE:mM:k:a0:a1:a2:a3:a4:a5:a6:a7:a8:a9:a10:a11:a12:a13:a14:a15:\
314s0:s1:s2:s3:s4:s5:s6:s7:s8:s9:s10:s11:s12:s13:s14:s15",
315)
316
317# make sure all particles for cascade production are stable
318for kf in idbeam:
319 kc = myPythia.Pycomp(kf)
320 myPythia.SetMDCY(kc, 1, 0)
321# make charm or beauty signal hadrons stable
322for kf in idsig:
323 kc = myPythia.Pycomp(kf)
324 myPythia.SetMDCY(kc, 1, 0)
325
326stack = 1000 * [0] # declare the stack for the cascade particles
327for iev in range(args.nevgen):
328 if iev % 1000 == 0:
329 print("Generate event ", iev)
330 nstack = 0
331 # put protons of energy pbeamh on the stack
332 # stack: PID, px, py, pz, cascade depth, nstack of mother
333 stack[nstack] = [2212, 0.0, 0.0, args.pbeamh, 1, 100 * [0], 100 * [0]]
334 stack[nstack][5][0] = 2212
335 while nstack >= 0:
336 # generate a signal based on probabilities in hists i*10+8?
337 ptot = ROOT.TMath.Sqrt(stack[nstack][1] ** 2 + stack[nstack][2] ** 2 + stack[nstack][3] ** 2)
338 prbsig = 0.0
339 for i in range(1, id + 1):
340 if stack[nstack][0] == idhist[i]: # get hist id for this beam particle
341 idpn = 0
342 if random.random() > fracp: # decide on p or n target
343 idpn = 1
344 idw = i * 10 + idpn * 4 + 4
345 ib = h[str(idw)].FindBin(ptot, 0.0, 0.0)
346 prbsig = h[str(idw)].GetBinContent(ib)
347 if prbsig > random.random():
348 # last particle on the stack as beam particle
349 for k in range(1, 4):
350 myPythia.SetP(1, k, stack[nstack][k])
351 myPythia.SetP(2, k, 0.0)
352 # new particle/momentum, init again: signal run.
353 myPythia.SetMSEL(args.mselcb) # set forced ccbar or bbbar generation
354 myPythia.Initialize("3MOM", PDG.GetParticle(stack[nstack][0]).GetName(), target[idpn], 0.0)
355 myPythia.GenerateEvent()
356 # look for the signal particles
357 charmFound = []
358 for itrk in range(1, myPythia.GetN() + 1):
359 idabs = ROOT.TMath.Abs(myPythia.GetK(itrk, 2))
360 if idabs in idsig: # if signal is found, store in ntuple
361 vl = array("f")
362 vl.append(float(myPythia.GetK(itrk, 2)))
363 for i in range(1, 6):
364 vl.append(float(myPythia.GetP(itrk, i)))
365 vl.append(float(myPythia.GetK(1, 2)))
366 for i in range(1, 6):
367 vl.append(float(myPythia.GetP(1, i)))
368 vl.append(float(stack[nstack][4]))
369 for i in range(16):
370 vl.append(float(stack[nstack][5][i]))
371 nsub = stack[nstack][4] - 1
372 if nsub > 15:
373 nsub = 15
374 for i in range(nsub):
375 vl.append(float(stack[nstack][6][i]))
376 vl.append(float(myPythia.GetMSTI(1)))
377 for i in range(nsub + 1, 16):
378 vl.append(float(0))
379 Ntup.Fill(vl)
380 charmFound.append(itrk)
381 h["1"].Fill(myPythia.GetP(itrk, 4))
382 h["2"].Fill(stack[nstack][4])
383 if idabs == 421 and stack[nstack][4] == 1:
384 # some checking hist to monitor pt**2, XF of prompt D^0
385 pt2 = myPythia.GetP(itrk, 1) ** 2 + myPythia.GetP(itrk, 2) ** 2
386 h["3"].Fill(pt2)
387 h["4"].Fill(pt2)
388 h["5"].Fill(ROOT.TMath.Sqrt(pt2))
389 # boost to Cm frame for XF ccalculation of D^0
390 beta = args.pbeamh / (myPythia.GetP(1, 4) + myPythia.GetP(2, 5))
391 gamma = (1 - beta**2) ** (-0.5)
392 pbcm = -gamma * beta * myPythia.GetP(1, 4) + gamma * myPythia.GetP(1, 3)
393 pDcm = -gamma * beta * myPythia.GetP(itrk, 4) + gamma * myPythia.GetP(itrk, 3)
394 xf = pDcm / pbcm
395 h["6"].Fill(xf)
396 if len(charmFound) > 0 and args.storePrimaries:
397 for itP in range(1, myPythia.GetN() + 1):
398 if itP in charmFound:
399 continue
400 if myPythia.GetK(itP, 1) == 1:
401 # store only undecayed particle and no charm found
402 # ***WARNING****: with new with new ancestor and process info (a0-15, s0-15) add to ntuple, might not work???
403 Ntup.Fill(
404 float(myPythia.GetK(itP, 2)),
405 float(myPythia.GetP(itP, 1)),
406 float(myPythia.GetP(itP, 2)),
407 float(myPythia.GetP(itP, 3)),
408 float(myPythia.GetP(itP, 4)),
409 float(myPythia.GetP(itP, 5)),
410 -1,
411 float(myPythia.GetV(itP, 1) - myPythia.GetV(charmFound[0], 1)),
412 float(myPythia.GetV(itP, 2) - myPythia.GetV(charmFound[0], 2)),
413 float(myPythia.GetV(itP, 3) - myPythia.GetV(charmFound[0], 3)),
414 0,
415 0,
416 stack[nstack][4],
417 )
418
419 # now generate msel=2 to add new cascade particles to the stack
420 for k in range(1, 4):
421 myPythia.SetP(1, k, stack[nstack][k])
422 myPythia.SetP(2, k, 0.0)
423 # new particle/momentum, init again, generate total cross-section event
424 myPythia.SetMSEL(2) # mbias event
425 idpn = 0
426 if random.random() > fracp:
427 idpn = 1
428 myPythia.Initialize("3MOM", PDG.GetParticle(stack[nstack][0]).GetName(), target[idpn], 0.0)
429 myPythia.GenerateEvent()
430 # remove used particle from the stack, before adding new
431 # first store its history: cascade depth and ancestors-list
432 icas = stack[nstack][4] + 1
433 if icas > 98:
434 icas = 98
435 anclist = copy.copy(stack[nstack][5])
436 sublist = copy.copy(stack[nstack][6])
437 # fill in interaction process of first proton
438 if nstack == 0:
439 sublist[0] = myPythia.GetMSTI(1)
440 nstack = nstack - 1
441 for itrk in range(1, myPythia.GetN() + 1):
442 if myPythia.GetK(itrk, 1) == 1:
443 ptot = ROOT.TMath.Sqrt(
444 myPythia.GetP(itrk, 1) ** 2 + myPythia.GetP(itrk, 2) ** 2 + myPythia.GetP(itrk, 3) ** 2
445 )
446 if ptot > pbeaml:
447 # produced particle is stable and has enough momentum, is it in the wanted list?
448 for isig in range(len(idbeam)):
449 if ROOT.TMath.Abs(myPythia.GetK(itrk, 2)) == idbeam[isig] and nstack < 999:
450 if nstack < 999:
451 nstack += 1
452 # update ancestor list
453 tmp = copy.copy(anclist)
454 tmp[icas - 1] = myPythia.GetK(itrk, 2)
455 stmp = copy.copy(sublist)
456 # Pythia interaction process identifier
457 stmp[icas - 1] = myPythia.GetMSTI(1)
458 stack[nstack] = [
459 myPythia.GetK(itrk, 2),
460 myPythia.GetP(itrk, 1),
461 myPythia.GetP(itrk, 2),
462 myPythia.GetP(itrk, 3),
463 icas,
464 tmp,
465 stmp,
466 ]
467
468print("Now at Ntup.Write()")
469Ntup.Write()
470for akey in h:
471 h[akey].Write()
472ftup.Close()
473
474timer.Stop()
475rtime = timer.RealTime()
476ctime = timer.CpuTime()
477print(" ")
478print("Macro finished successfully.")
479print(f"Output file is {ftup.GetName()}")
480print(f"Real time {rtime} s, CPU time {ctime} s")
None fillp1(hist)
Definition: makeCascade.py:199
None PoorE791_tune(P6)
Definition: makeCascade.py:139
None LHCb_tune(P6)
Definition: makeCascade.py:163