PYTHIA  8.313
PythiaCascade.h
1 // PythiaCascade.h is a part of the PYTHIA event generator.
2 // Copyright (C) 2025 Torbjorn Sjostrand.
3 // PYTHIA is licenced under the GNU GPL v2 or later, see COPYING for details.
4 // Please respect the MCnet Guidelines, see GUIDELINES for details.
5 // Author: Torbjorn Sjostrand.
6 
7 #ifndef Pythia8_PythiaCascade_H
8 #define Pythia8_PythiaCascade_H
9 
10 #include "Pythia8/Pythia.h"
11 
12 namespace Pythia8 {
13 
14 //==========================================================================
15 
16 // Wrapper class for Pythia usage in cosmic ray or detector cascades.
17 // Here it is assumed that the user wants to control the full
18 // evolution. The complete event record is then kept somewhere
19 // separate from PYTHIA, and specific input on production and decay
20 // generates the next subevent.
21 
22 // Intended flow:
23 
24 // - init sets up all generation, given a maximum energy. This may be
25 // time-consuming, less so if MPI initialization data can be reused.
26 
27 // - sigmaSetuphN prepares a possible collision for a given hadron by
28 // calculating the hadron-nucleon cross section (if possible).
29 // Moderately time-consuming.
30 
31 // - sigmahA uses the hN cross section calculated by sigaSetuphN and
32 // returns hadron-ion cross section for a collision with a specified
33 // nucleus. It can be called several times to cover a mix of target
34 // nuclei, with minimal time usage.
35 
36 // - nextColl performs the hadron-nucleus collision, as a sequences of
37 // hadron-nucleon ones. Can be quite time-consuming.
38 
39 // - nextDecay can be used anytime to decay a particle. Each
40 // individual decay is rather fast, but there may be many of them.
41 
42 // - stat can be used at end of run to give a summary of error
43 // messages. Negligible time usage.
44 
45 // - references to particleData() and rndm() can be used in the main
46 // - program.
47 
48 // Note: when a hadron interacts with a medium particle, the latter is
49 // added to the event record. This program uses these additional
50 // status codes for target particles in the medium:
51 // 181: the first (or only) target nucleon in a collision.
52 // 182: nucleons from further subcollisions with the same target nucleus.
53 
54 // Warning: the large boosts involved at the higher cosmic ray
55 // energies are not perfectly managed, which leads to non-negligible
56 // energy-momentum non-conservation. The worst (sub)events are caught
57 // and regenerated.
58 
59 //--------------------------------------------------------------------------
60 
62 
63 public:
64 
65  // Default constructor, all setup is done in init().
66  PythiaCascade() = default;
67 
68  //--------------------------------------------------------------------------
69 
70  // Initialize PythiaCascade for a given maximal incoming energy.
71 
72  // Set eMax to the maximal incoming energy (in GeV) on fixed target.
73 
74  // Keep listFinal = false if you want to get back the full event
75  // record, else only the "final" particles of the collision or decay
76  // are returned.
77 
78  // Keep rapidDecays = false if you want to do every decay yourself,
79  // else all particle types with a tau0 below smallTau0 will be
80  // decayed immediately. The tau0 units are mm/c, so default gives c
81  // * tau0 = 1e-10 mm = 100 fm. Note that time dilation effects are
82  // not included, and they can be large.
83 
84  // The reuseMPI argument mimics MultipartonInteractions:reuseInit,
85  // but streamlined into three main options;
86  // 0 = current run is self-contained, so MPI is initialized from scratch,
87  // but MPI data is stored on initFile for future use;
88  // 3 = read MPI initialization data from initFile, if it exists,
89  // and else initialize and save the MPI data on initFile;
90  // -1 = read MPI initialization data and other run settings from
91  // a .cmnd file, if not empty.
92  // In each case, the saved initialization must have been done with
93  // the same or a larger eMax than the one now being used.
94 
95  bool init(double eMaxIn = 1e9, bool listFinalIn = false,
96  bool rapidDecaysIn = false, double smallTau0In = 1e-10,
97  int reuseMPI = 3, string initFile = "pythiaCascade.mpi") {
98 
99  // Store input for future usage.
100  eMax = eMaxIn;
101  listFinal = listFinalIn;
102  rapidDecays = rapidDecaysIn;
103  smallTau0 = smallTau0In;
104 
105  // Proton mass.
106  mp = pythiaMain.particleData.m0(2212);
107 
108  // Main Pythia object for managing the cascade evolution in a
109  // nucleus. Can also do decays, but no hard processes.
110  pythiaMain.readString("ProcessLevel:all = off");
111  pythiaMain.readString("13:mayDecay = on");
112  pythiaMain.readString("211:mayDecay = on");
113  pythiaMain.readString("321:mayDecay = on");
114  pythiaMain.readString("130:mayDecay = on");
115  pythiaMain.settings.flag("ParticleDecays:limitTau0", rapidDecays);
116  pythiaMain.settings.parm("ParticleDecays:tau0Max", smallTau0);
117 
118  // Redure statistics printout to relevant ones.
119  pythiaMain.readString("Stat:showProcessLevel = off");
120  pythiaMain.readString("Stat:showPartonLevel = off");
121 
122  // Initialize. Return if failure.
123  if (!pythiaMain.init()) return false;
124 
125  if ( reuseMPI < 0 ) {
126  pythiaColl.readFile(initFile);
127  initFile = "";
128  }
129 
130  // Secondary Pythia object for performing individual collisions,
131  // or decays. Variable incoming beam type and energy.
132  pythiaColl.readString("Beams:allowVariableEnergy = on");
133  pythiaColl.readString("Beams:allowIDAswitch = on");
134 
135  // Set up for fixed-target collisions.
136  pythiaColl.readString("Beams:frameType = 3");
137  pythiaColl.settings.parm("Beams:pzA", eMax);
138  pythiaColl.readString("Beams:pzB = 0.");
139 
140  // Must use the soft and low-energy QCD processes.
141  pythiaColl.readString("SoftQCD:all = on");
142  pythiaColl.readString("LowEnergyQCD:all = on");
143 
144  // Primary (single) decay to be done by pythiaColl, to circumvent
145  // limitTau0.
146  pythiaColl.readString("13:mayDecay = on");
147  pythiaColl.readString("211:mayDecay = on");
148  pythiaColl.readString("321:mayDecay = on");
149  pythiaColl.readString("130:mayDecay = on");
150 
151  // Secondary decays to be done by pythiaMain, respecting limitTau0.
152  pythiaColl.readString("HadronLevel:Decay = off");
153 
154  // Reduce printout and relax energy-momentum conservation.
155  // (Unusually large errors unfortunate consequence of large boosts.)
156  pythiaColl.readString("Print:quiet = on");
157  pythiaColl.readString("Check:epTolErr = 0.01");
158  pythiaColl.readString("Check:epTolWarn = 0.0001");
159  pythiaColl.readString("Check:mTolErr = 0.01");
160 
161  // Redure statistics printout to relevant ones.
162  pythiaColl.readString("Stat:showProcessLevel = off");
163  pythiaColl.readString("Stat:showPartonLevel = off");
164 
165  // Reuse MPI initialization file if it exists; else create a new one.
166  if (reuseMPI > 0)
167  pythiaColl.readString("MultipartonInteractions:reuseInit = 3");
168  else if (reuseMPI == 0)
169  pythiaColl.readString("MultipartonInteractions:reuseInit = 1");
170  if (reuseMPI >= 0)
171  pythiaColl.settings.word("MultipartonInteractions:initFile", initFile);
172 
173  // Initialize.
174  if (!pythiaColl.init()) return false;
175  return true;
176 
177  }
178 
179  //--------------------------------------------------------------------------
180 
181  // Calculate the average number of collisions. Average number of
182  // hadron-nucleon collisions in a hadron-nucleus one. If not in
183  // table then interpolate, knowing that <n> - 1 propto A^{2/3}.
184 
185  double nCollAvg(int A) {
186  for (int i = 0; i < nA; ++i) {
187  if (A == tabA[i]) {
188  return (sigmaNow < tabBorder) ? 1. + tabSlopeLo[i] * sigmaNow
189  : 1. + tabOffset[i] + tabSlope[i] * sigmaNow;
190  } else if (A < tabA[i]) {
191  double nColl1 = (sigmaNow < tabBorder) ? tabSlopeLo[i - 1] * sigmaNow
192  : tabOffset[i - 1] + tabSlope[i - 1] * sigmaNow;
193  double nColl2 = (sigmaNow < tabBorder) ? tabSlopeLo[i] * sigmaNow
194  : tabOffset[i] + tabSlope[i] * sigmaNow;
195  double wt1 = (tabA[i] - A) / (tabA[i] - tabA[i - 1]);
196  return 1. + wt1 * pow( A / tabA[i - 1], 2./3.) * nColl1
197  + (1. - wt1) * pow( A / tabA[i], 2./3.) * nColl2;
198  }
199  }
200 
201  return numeric_limits<double>::quiet_NaN();
202  }
203 
204  //--------------------------------------------------------------------------
205 
206  // Calculate the hadron-proton collision cross section.
207  // Return false if not possible to find.
208 
209  bool sigmaSetuphN(int idNowIn, Vec4 pNowIn, double mNowIn) {
210 
211  // Cannot handle low-energy hadrons.
212  if (pNowIn.e() - mNowIn < eKinMin) return false;
213 
214  // Cannot handle hadrons above maximum energy set at initialization.
215  if (pNowIn.e() > eMax) {
216  logger.ERROR_MSG("too high energy");
217  return false;
218  }
219 
220  // Save incoming quantities for reuse in later methods.
221  idNow = idNowIn;
222  pNow = pNowIn;
223  mNow = mNowIn;
224 
225  // Calculate hadron-nucleon cross section. Check if cross section
226  // vanishes.
227  eCMNow = (pNow + Vec4(0, 0, 0, mp)).mCalc();
228  sigmaNow = pythiaColl.getSigmaTotal(idNow, 2212, eCMNow, mNow, mp);
229  if (sigmaNow <= 0.) {
230  if (eCMNow - mNow - mp > eKinMin)
231  logger.ERROR_MSG("vanishing cross section");
232  return false;
233  }
234 
235  // Done.
236  return true;
237 
238  }
239 
240  //--------------------------------------------------------------------------
241 
242  // Calculate the hadron-nucleus cross section for a given nucleon
243  // number A, using hN cross section from sigmaSetuphN. Interpolate
244  // where not (yet) available.
245 
246  double sigmahA(int A) {
247 
248  // Restrict to allowed range 1 <= A <= 208.
249  if (A < 1 || A > 208) {
250  logger.ERROR_MSG("A is outside of valid range (1 <= A <= 208)");
251  return 0.;
252  }
253 
254  // Correction factor for number of h-nucleon collisions per
255  // h-nucleus one.
256  double sigmahA = A * sigmaNow / nCollAvg(A);
257 
258  // Done.
259  return sigmahA;
260 
261  }
262 
263  //--------------------------------------------------------------------------
264 
265  // Generate a collision, and return the event record.
266  // Input (Z, A) of nucleus, and optionally collision vertex.
267 
268  Event& nextColl(int Znow, int Anow, Vec4 vNow = Vec4() ) {
269 
270  // References to the two event records. Clear main event record.
271  Event& eventMain = pythiaMain.event;
272  Event& eventColl = pythiaColl.event;
273  eventMain.clear();
274 
275  // Restrict to allowed range 1 <= A <= 208.
276  if (Anow < 1 || Anow > 208) {
277  logger.ERROR_MSG("A is outside of valid range (1 <= A <= 208)");
278  return eventMain;
279  }
280 
281  // Insert incoming particle in cleared main event record.
282  eventMain.append(90, -11, 0, 0, 1, 1, 0, 0, pNow, mNow);
283  int iHad = eventMain.append(idNow, 12, 0, 0, 0, 0, 0, 0, pNow, mNow);
284  eventMain[iHad].vProd(vNow);
285 
286  // Set up for collisions on a nucleus.
287  int np = Znow;
288  int nn = Anow - Znow;
289  int sizeOld = 0;
290  int sizeNew = 0;
291  Vec4 dirNow = pNow / pNow.pAbs();
292  Rndm& rndm = pythiaMain.rndm;
293 
294  // Drop rate of geometric series. (Deuterium has corrected nCollAvg.)
295  double probMore = 1. - 1. / nCollAvg(Anow);
296 
297  // Loop over varying number of hit nucleons in target nucleus.
298  for (int iColl = 1; iColl <= Anow; ++iColl) {
299  if (iColl > 1 && rndm.flat() > probMore) break;
300 
301  // Pick incoming projectile: trivial for first subcollision, else ...
302  int iProj = iHad;
303  int procType = 0;
304 
305  // ... find highest-pLongitudinal particle from latest subcollision.
306  if (iColl > 1) {
307  iProj = 0;
308  double pMax = 0.;
309  for (int i = sizeOld; i < sizeNew; ++i)
310  if ( eventMain[i].isFinal() && eventMain[i].isHadron()) {
311  double pp = dot3(dirNow, eventMain[i].p());
312  if (pp > pMax) {
313  iProj = i;
314  pMax = pp;
315  }
316  }
317 
318  // No further subcollision if no particle with enough energy.
319  if ( iProj == 0 || eventMain[iProj].e() - eventMain[iProj].m()
320  < eKinMin) break;
321 
322  // Choose process; only SD or ND at perturbative energies.
323  double eCMSub = (eventMain[iProj].p() + Vec4(0, 0, 0, mp)).mCalc();
324  if (eCMSub > 10.) procType = (rndm.flat() < probSD) ? 4 : 1;
325  }
326 
327  // Pick one p or n from target.
328  int idProj = eventMain[iProj].id();
329  bool doProton = rndm.flat() < (np / double(np + nn));
330  if (doProton) np -= 1;
331  else nn -= 1;
332  int idNuc = (doProton) ? 2212 : 2112;
333 
334  // Do a projectile-nucleon subcollision. Return empty event if failure.
335  pythiaColl.setBeamIDs(idProj, idNuc);
336  pythiaColl.setKinematics(eventMain[iProj].p(), Vec4());
337  if (!pythiaColl.next(procType)) {
338  eventMain.clear();
339  return eventMain;
340  }
341 
342  // Insert target nucleon. Mothers are (0,iProj) to mark who it
343  // interacted with. Always use proton mass for simplicity.
344  int statusNuc = (iColl == 1) ? -181 : -182;
345  int iNuc = eventMain.append( idNuc, statusNuc, 0, iProj, 0, 0, 0, 0,
346  0., 0., 0., mp, mp);
347  eventMain[iNuc].vProdAdd(vNow);
348 
349  // Update full energy of the event with the proton mass.
350  eventMain[0].e( eventMain[0].e() + mp);
351  eventMain[0].m( eventMain[0].p().mCalc() );
352 
353  // Insert secondary produced particles (but skip intermediate partons)
354  // into main event record and shift to correct production vertex.
355  sizeOld = eventMain.size();
356  for (int iSub = 3; iSub < eventColl.size(); ++iSub) {
357  if (!eventColl[iSub].isFinal()) continue;
358  int iNew = eventMain.append(eventColl[iSub]);
359  eventMain[iNew].mothers(iNuc, iProj);
360  eventMain[iNew].vProdAdd(vNow);
361  }
362  sizeNew = eventMain.size();
363 
364  // Update daughters of colliding hadrons and other history.
365  eventMain[iProj].daughters(sizeOld, sizeNew - 1);
366  eventMain[iNuc].daughters(sizeOld, sizeNew - 1);
367  eventMain[iProj].statusNeg();
368  eventMain[iProj].tau(0.);
369 
370  // End of loop over interactions in a nucleus.
371  }
372 
373  // Optionally do decays of short-lived particles.
374  if (rapidDecays) pythiaMain.moreDecays();
375 
376  // Optionally compress event record.
377  if (listFinal) compress();
378 
379  // Return generated collision.
380  return eventMain;
381 
382  }
383 
384  //--------------------------------------------------------------------------
385 
386  // Generate a particle decay, and return the event record.
387  // You can allow sequential decays, if they occur rapidly enough.
388 
389  Event& nextDecay(int idNowIn, Vec4 pNowIn, double mNowIn,
390  Vec4 vNow = Vec4() ) {
391 
392  // Save incoming quantities. (Not needed, but by analogy with
393  // collisions.)
394  idNow = idNowIn;
395  pNow = pNowIn;
396  mNow = mNowIn;
397 
398  // References to the event records. Clear them.
399  Event& eventMain = pythiaMain.event;
400  Event& eventColl = pythiaColl.event;
401  eventMain.clear();
402  eventColl.clear();
403 
404  // Insert incoming particle in cleared collision event record.
405  eventColl.append(90, -11, 0, 0, 1, 1, 0, 0, pNow, mNow);
406  int iHad = eventColl.append(idNow, 12, 0, 0, 0, 0, 0, 0, pNow, mNow);
407  eventColl[iHad].vProd(vNow);
408 
409  // Decay incoming particle. Return empty event if fail. Copy event record.
410  if (!pythiaColl.moreDecays(iHad)) return eventMain;
411  eventMain = eventColl;
412 
413  // Optionally do secondary decays of short-lived particles.
414  if (rapidDecays) pythiaMain.moreDecays();
415 
416  // Optionally compress event record.
417  if (listFinal) compress();
418 
419  // Return generated collision.
420  return eventMain;
421 
422  }
423 
424  //--------------------------------------------------------------------------
425 
426  // Compress the event record by removing initial and intermediate
427  // particles. Keep line 0, since the += operator for event records
428  // only adds from 1 on.
429 
430  void compress() {
431 
432  // Reference to the main event record. Original and new size.
433  Event& eventMain = pythiaMain.event;
434  int sizeOld = eventMain.size();
435  int sizeNew = 1;
436 
437  // Loop through all particles and move up the final ones to the top.
438  // Remove history information. Update event record size.
439  for (int i = 1; i < sizeOld; ++i) if (eventMain[i].isFinal()) {
440  eventMain[sizeNew] = eventMain[i];
441  eventMain[sizeNew].mothers( 0, 0);
442  eventMain[sizeNew].daughters( 0, 0);
443  ++sizeNew;
444  }
445 
446  // Shrink event record to new size.
447  eventMain.popBack( sizeOld - sizeNew);
448 
449  }
450 
451  //--------------------------------------------------------------------------
452 
453  // Summary of aborts, errors and warnings.
454 
455  void stat() {
456  pythiaMain.stat();
457  pythiaColl.stat();
458  logger.errorStatistics();
459  }
460 
461  //--------------------------------------------------------------------------
462 
463  // Possibility to access particle data and random numbers from
464  // pythiaMain.
465 
466  ParticleData& particleData() {return pythiaMain.particleData;}
467 
468  Rndm& rndm() {return pythiaMain.rndm;}
469 
470 //--------------------------------------------------------------------------
471 
472 private:
473 
474  // The Pythia instances used for decays and for collisions. Could
475  // be made public, but cleaner to allow only limited access as
476  // above.
477  Pythia pythiaMain, pythiaColl;
478 
479  // Logger instance for errors in this class.
480  Logger logger;
481 
482  // Save quantities.
483  bool listFinal, rapidDecays;
484  int idNow;
485  double eMax, smallTau0, mp, mNow, eCMNow, sigmaNow;
486  Vec4 pNow;
487 
488  // Fraction of single diffractive events beyond first collision in nucleus.
489  static constexpr double probSD = 0.3;
490 
491  // Hadrons below the kinetic energy threshold cannot interact with medium.
492  // (At least not using Pythia.) Used both in lab and in CM frame.
493  static constexpr double eKinMin = 0.2;
494 
495  // Studied nuclei by A number, with offset and slope of <nColl>(sigma):
496  // 1H, 2H, 4He, 9Be, 12C, 14N, 16O, 27Al, 40Ar, 56Fe, 63Cu, 84Kr,
497  // 107Ag, 129Xe, 197Au, 208Pb.
498  static constexpr int nA = 16;
499  static constexpr double tabBorder = 20.;
500  static const double tabA[nA];
501  static const double tabOffset[nA];
502  static const double tabSlope[nA];
503  static const double tabSlopeLo[nA];
504 
505 };
506 
507 // Constant definitions.
508 const double PythiaCascade::tabA[] = {1, 2, 4, 9, 12, 14, 16, 27, 40, 56,
509  63, 84, 107, 129, 197, 208};
510 const double PythiaCascade::tabOffset[] = {0., 0.03, 0.08, 0.15, 0.20, 0.20,
511  0.20, 0.26, 0.30, 0.34, 0.40, 0.40, 0.40, 0.50, 0.50, 0.60};
512 const double PythiaCascade::tabSlope[] = {0., 0.0016, 0.0033, 0.0075, 0.0092,
513  0.0105, 0.012, 0.017, 0.022, 0.027, 0.028, 0.034, 0.040, 0.044, 0.055,
514  0.055};
515 const double PythiaCascade::tabSlopeLo[] = {0., 0.0031, 0.0073, 0.015, 0.0192,
516  0.0205, 0.022, 0.03, 0.037, 0.044, 0.048, 0.054, 0.06, 0.069, 0.08, 0.085};
517 
518 //==========================================================================
519 
520 } // end namespace Pythia8
521 
522 #endif // end Pythia8_PythiaCascade_H
double nCollAvg(int A)
Definition: PythiaCascade.h:185
bool flag(string keyIn)
Give back current value, with check that key exists.
Definition: Settings.cc:1599
PythiaCascade()=default
Default constructor, all setup is done in init().
Rndm rndm
Random number generator.
Definition: Pythia.h:380
bool readString(string line, bool warn=true, int subrun=SUBRUNDEFAULT)
Read in one update for a setting or particle data from a single line.
Definition: Pythia.h:98
ParticleData particleData
ParticleData: the particle data table/database.
Definition: Pythia.h:377
The Event class holds all info on the generated event.
Definition: Event.h:408
Settings settings
Settings: databases of flags/modes/parms/words to control run.
Definition: Pythia.h:374
void clear()
Clear event record.
Definition: Event.h:428
bool setKinematics(double eCMIn)
Switch beam kinematics.
Definition: Pythia.cc:1335
Event event
The event record for the complete event history.
Definition: Pythia.h:365
bool init()
Initialize.
Definition: Pythia.cc:402
double dot3(const Vec4 &v1, const Vec4 &v2)
Scalar and cross product of 3-vector parts.
Definition: Basics.cc:618
void stat()
Summary of aborts, errors and warnings.
Definition: PythiaCascade.h:455
Definition: Logger.h:23
double sigmahA(int A)
Definition: PythiaCascade.h:246
bool setBeamIDs(int idAin, int idBin=0)
Switch to new beam particle identities; for similar hadrons only.
Definition: Pythia.cc:1313
Definition: Basics.h:388
bool moreDecays()
Special routine to allow more decays if on/off switches changed.
Definition: Pythia.h:295
int append(Particle entryIn)
Put a new particle at the end of the event record; return index.
Definition: Event.h:462
Event & nextDecay(int idNowIn, Vec4 pNowIn, double mNowIn, Vec4 vNow=Vec4())
Definition: PythiaCascade.h:389
bool sigmaSetuphN(int idNowIn, Vec4 pNowIn, double mNowIn)
Definition: PythiaCascade.h:209
Intended flow:
Definition: PythiaCascade.h:61
void stat()
Main routine to provide final statistics on generation.
Definition: Pythia.cc:1663
void popBack(int nRemove=1)
Remove last n entries.
Definition: Event.h:516
int size() const
Event record size.
Definition: Event.h:459
bool readFile(string fileName, bool warn=true, int subrun=SUBRUNDEFAULT)
Read in updates for settings or particle data from user-defined file.
Definition: Pythia.h:102
double getSigmaTotal()
Get total cross section for two hadrons in the event record or standalone.
Definition: Pythia.h:308
Header for classes to set beam momentum and interaction vertex spread.
Definition: Analysis.h:20
The Pythia class contains the top-level routines to generate an event.
Definition: Pythia.h:71
double m(const Vec4 &v1)
Invariant mass and its square.
Definition: Basics.cc:588
void compress()
Definition: PythiaCascade.h:430
ParticleData & particleData()
Definition: PythiaCascade.h:466
bool next()
Generate the next event.
Definition: Pythia.h:271
double flat()
Generate next random number uniformly between 0 and 1.
Definition: Basics.cc:189
bool init(double eMaxIn=1e9, bool listFinalIn=false, bool rapidDecaysIn=false, double smallTau0In=1e-10, int reuseMPI=3, string initFile="pythiaCascade.mpi")
Initialize PythiaCascade for a given maximal incoming energy.
Definition: PythiaCascade.h:95
void errorStatistics() const
Print error statistics.
Definition: Logger.h:113
This class holds a map of all ParticleDataEntries.
Definition: ParticleData.h:422
Definition: Basics.h:32
Event & nextColl(int Znow, int Anow, Vec4 vNow=Vec4())
Definition: PythiaCascade.h:268