Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-09-15 09:06:57

0001 // PythiaCascade.h is a part of the PYTHIA event generator.
0002 // Copyright (C) 2025 Torbjorn Sjostrand.
0003 // PYTHIA is licenced under the GNU GPL v2 or later, see COPYING for details.
0004 // Please respect the MCnet Guidelines, see GUIDELINES for details.
0005 // Author: Torbjorn Sjostrand.
0006 
0007 #ifndef Pythia8_PythiaCascade_H
0008 #define Pythia8_PythiaCascade_H
0009 
0010 #include "Pythia8/Pythia.h"
0011 
0012 namespace Pythia8 {
0013 
0014 //==========================================================================
0015 
0016 // Wrapper class for Pythia usage in cosmic ray or detector cascades.
0017 // Here it is assumed that the user wants to control the full
0018 // evolution.  The complete event record is then kept somewhere
0019 // separate from PYTHIA, and specific input on production and decay
0020 // generates the next subevent.
0021 
0022 // Intended flow:
0023 
0024 // - init sets up all generation, given a maximum energy. This may be
0025 //   time-consuming, less so if MPI initialization data can be reused.
0026 
0027 // - sigmaSetuphN prepares a possible collision for a given hadron by
0028 //   calculating the hadron-nucleon cross section (if possible).
0029 //   Moderately time-consuming.
0030 
0031 // - sigmahA uses the hN cross section calculated by sigaSetuphN and
0032 //   returns hadron-ion cross section for a collision with a specified
0033 //   nucleus.  It can be called several times to cover a mix of target
0034 //   nuclei, with minimal time usage.
0035 
0036 // - nextColl performs the hadron-nucleus collision, as a sequences of
0037 //   hadron-nucleon ones. Can be quite time-consuming.
0038 
0039 // - nextDecay can be used anytime to decay a particle. Each
0040 //   individual decay is rather fast, but there may be many of them.
0041 
0042 // - stat can be used at end of run to give a summary of error
0043 //   messages.  Negligible time usage.
0044 
0045 // - references to particleData() and rndm() can be used in the main
0046 // - program.
0047 
0048 // Note: when a hadron interacts with a medium particle, the latter is
0049 // added to the event record. This program uses these additional
0050 // status codes for target particles in the medium:
0051 //  181: the first (or only) target nucleon in a collision.
0052 //  182: nucleons from further subcollisions with the same target nucleus.
0053 
0054 // Warning: the large boosts involved at the higher cosmic ray
0055 // energies are not perfectly managed, which leads to non-negligible
0056 // energy-momentum non-conservation. The worst (sub)events are caught
0057 // and regenerated.
0058 
0059 //--------------------------------------------------------------------------
0060 
0061 class PythiaCascade {
0062 
0063 public:
0064 
0065   // Default constructor, all setup is done in init().
0066   PythiaCascade() = default;
0067 
0068   //--------------------------------------------------------------------------
0069 
0070   // Initialize PythiaCascade for a given maximal incoming energy.
0071 
0072   // Set eMax to the maximal incoming energy (in GeV) on fixed target.
0073 
0074   // Keep listFinal = false if you want to get back the full event
0075   // record, else only the "final" particles of the collision or decay
0076   // are returned.
0077 
0078   // Keep rapidDecays = false if you want to do every decay yourself,
0079   // else all particle types with a tau0 below smallTau0 will be
0080   // decayed immediately.  The tau0 units are mm/c, so default gives c
0081   // * tau0 = 1e-10 mm = 100 fm.  Note that time dilation effects are
0082   // not included, and they can be large.
0083 
0084   // The reuseMPI argument mimics MultipartonInteractions:reuseInit,
0085   // but streamlined into three main options;
0086   // 0 = current run is self-contained, so MPI is initialized from scratch,
0087   //     but MPI data is stored on initFile for future use;
0088   // 3 = read MPI initialization data from initFile, if it exists,
0089   //     and else initialize and save the MPI data on initFile;
0090   // -1 = read MPI initialization data and other run settings from
0091   //    a .cmnd file, if not empty.
0092   // In each case, the saved initialization must have been done with
0093   // the same or a larger eMax than the one now being used.
0094 
0095   bool init(double eMaxIn = 1e9, bool listFinalIn = false,
0096     bool rapidDecaysIn = false, double smallTau0In = 1e-10,
0097     int reuseMPI = 3, string initFile = "pythiaCascade.mpi") {
0098 
0099     // Store input for future usage.
0100     eMax        = eMaxIn;
0101     listFinal   = listFinalIn;
0102     rapidDecays = rapidDecaysIn;
0103     smallTau0   = smallTau0In;
0104 
0105     // Proton mass.
0106     mp      = pythiaMain.particleData.m0(2212);
0107 
0108     // Main Pythia object for managing the cascade evolution in a
0109     // nucleus. Can also do decays, but no hard processes.
0110     pythiaMain.readString("ProcessLevel:all = off");
0111     pythiaMain.readString("13:mayDecay  = on");
0112     pythiaMain.readString("211:mayDecay = on");
0113     pythiaMain.readString("321:mayDecay = on");
0114     pythiaMain.readString("130:mayDecay = on");
0115     pythiaMain.settings.flag("ParticleDecays:limitTau0", rapidDecays);
0116     pythiaMain.settings.parm("ParticleDecays:tau0Max", smallTau0);
0117 
0118     // Redure statistics printout to relevant ones.
0119     pythiaMain.readString("Stat:showProcessLevel = off");
0120     pythiaMain.readString("Stat:showPartonLevel = off");
0121 
0122     // Initialize. Return if failure.
0123     if (!pythiaMain.init()) return false;
0124 
0125     if ( reuseMPI < 0 ) {
0126       pythiaColl.readFile(initFile);
0127       initFile = "";
0128     }
0129 
0130     // Secondary Pythia object for performing individual collisions,
0131     // or decays. Variable incoming beam type and energy.
0132     pythiaColl.readString("Beams:allowVariableEnergy = on");
0133     pythiaColl.readString("Beams:allowIDAswitch = on");
0134 
0135     // Set up for fixed-target collisions.
0136     pythiaColl.readString("Beams:frameType = 3");
0137     pythiaColl.settings.parm("Beams:pzA", eMax);
0138     pythiaColl.readString("Beams:pzB = 0.");
0139 
0140     // Must use the soft and low-energy QCD processes.
0141     pythiaColl.readString("SoftQCD:all = on");
0142     pythiaColl.readString("LowEnergyQCD:all = on");
0143 
0144     // Primary (single) decay to be done by pythiaColl, to circumvent
0145     // limitTau0.
0146     pythiaColl.readString("13:mayDecay  = on");
0147     pythiaColl.readString("211:mayDecay = on");
0148     pythiaColl.readString("321:mayDecay = on");
0149     pythiaColl.readString("130:mayDecay = on");
0150 
0151     // Secondary decays to be done by pythiaMain, respecting limitTau0.
0152     pythiaColl.readString("HadronLevel:Decay = off");
0153 
0154     // Reduce printout and relax energy-momentum conservation.
0155     // (Unusually large errors unfortunate consequence of large boosts.)
0156     pythiaColl.readString("Print:quiet = on");
0157     pythiaColl.readString("Check:epTolErr = 0.01");
0158     pythiaColl.readString("Check:epTolWarn = 0.0001");
0159     pythiaColl.readString("Check:mTolErr = 0.01");
0160 
0161     // Redure statistics printout to relevant ones.
0162     pythiaColl.readString("Stat:showProcessLevel = off");
0163     pythiaColl.readString("Stat:showPartonLevel = off");
0164 
0165     // Reuse MPI initialization file if it exists; else create a new one.
0166     if (reuseMPI > 0)
0167       pythiaColl.readString("MultipartonInteractions:reuseInit = 3");
0168     else if (reuseMPI == 0)
0169       pythiaColl.readString("MultipartonInteractions:reuseInit = 1");
0170     if (reuseMPI >= 0)
0171       pythiaColl.settings.word("MultipartonInteractions:initFile", initFile);
0172 
0173     // Initialize.
0174     if (!pythiaColl.init()) return false;
0175     return true;
0176 
0177   }
0178 
0179   //--------------------------------------------------------------------------
0180 
0181   // Calculate the average number of collisions. Average number of
0182   // hadron-nucleon collisions in a hadron-nucleus one. If not in
0183   // table then interpolate, knowing that <n> - 1 propto A^{2/3}.
0184 
0185   double nCollAvg(int A) {
0186     for (int i = 0; i < nA; ++i) {
0187       if (A == tabA[i]) {
0188         return (sigmaNow < tabBorder) ? 1. + tabSlopeLo[i] * sigmaNow
0189           : 1. + tabOffset[i] + tabSlope[i] * sigmaNow;
0190       } else if (A < tabA[i]) {
0191         double nColl1 = (sigmaNow < tabBorder) ? tabSlopeLo[i - 1] * sigmaNow
0192           : tabOffset[i - 1] + tabSlope[i - 1] * sigmaNow;
0193         double nColl2 = (sigmaNow < tabBorder) ? tabSlopeLo[i] * sigmaNow
0194           : tabOffset[i] + tabSlope[i] * sigmaNow;
0195         double wt1 = (tabA[i] - A) / (tabA[i] - tabA[i - 1]);
0196         return 1. + wt1 * pow( A / tabA[i - 1], 2./3.) * nColl1
0197           + (1. - wt1) * pow( A / tabA[i], 2./3.) * nColl2;
0198       }
0199     }
0200 
0201     return numeric_limits<double>::quiet_NaN();
0202   }
0203 
0204   //--------------------------------------------------------------------------
0205 
0206   // Calculate the hadron-proton collision cross section.
0207   // Return false if not possible to find.
0208 
0209   bool sigmaSetuphN(int idNowIn, Vec4 pNowIn, double mNowIn) {
0210 
0211     // Cannot handle low-energy hadrons.
0212     if (pNowIn.e() - mNowIn < eKinMin) return false;
0213 
0214     // Cannot handle hadrons above maximum energy set at initialization.
0215     if (pNowIn.e() > eMax) {
0216       logger.ERROR_MSG("too high energy");
0217       return false;
0218     }
0219 
0220     // Save incoming quantities for reuse in later methods.
0221     idNow = idNowIn;
0222     pNow  = pNowIn;
0223     mNow  = mNowIn;
0224 
0225     // Calculate hadron-nucleon cross section. Check if cross section
0226     // vanishes.
0227     eCMNow = (pNow + Vec4(0, 0, 0, mp)).mCalc();
0228     sigmaNow = pythiaColl.getSigmaTotal(idNow, 2212, eCMNow, mNow, mp);
0229     if (sigmaNow <= 0.) {
0230       if (eCMNow - mNow - mp > eKinMin)
0231         logger.ERROR_MSG("vanishing cross section");
0232        return false;
0233     }
0234 
0235     // Done.
0236     return true;
0237 
0238   }
0239 
0240   //--------------------------------------------------------------------------
0241 
0242   // Calculate the hadron-nucleus cross section for a given nucleon
0243   // number A, using hN cross section from sigmaSetuphN. Interpolate
0244   // where not (yet) available.
0245 
0246   double sigmahA(int A) {
0247 
0248     // Restrict to allowed range 1 <= A <= 208.
0249     if (A < 1 || A > 208) {
0250       logger.ERROR_MSG("A is outside of valid range (1 <= A <= 208)");
0251       return 0.;
0252     }
0253 
0254     // Correction factor for number of h-nucleon collisions per
0255     // h-nucleus one.
0256     double sigmahA = A * sigmaNow / nCollAvg(A);
0257 
0258     // Done.
0259     return sigmahA;
0260 
0261   }
0262 
0263   //--------------------------------------------------------------------------
0264 
0265   // Generate a collision, and return the event record.
0266   // Input (Z, A) of nucleus, and optionally collision vertex.
0267 
0268   Event& nextColl(int Znow, int Anow, Vec4 vNow = Vec4() ) {
0269 
0270     // References to the two event records. Clear main event record.
0271     Event& eventMain = pythiaMain.event;
0272     Event& eventColl = pythiaColl.event;
0273     eventMain.clear();
0274 
0275     // Restrict to allowed range 1 <= A <= 208.
0276     if (Anow < 1 || Anow > 208) {
0277       logger.ERROR_MSG("A is outside of valid range (1 <= A <= 208)");
0278       return eventMain;
0279     }
0280 
0281     // Insert incoming particle in cleared main event record.
0282     eventMain.append(90,   -11, 0, 0, 1, 1, 0, 0, pNow, mNow);
0283     int iHad = eventMain.append(idNow, 12, 0, 0, 0, 0, 0, 0, pNow, mNow);
0284     eventMain[iHad].vProd(vNow);
0285 
0286     // Set up for collisions on a nucleus.
0287     int np      = Znow;
0288     int nn      = Anow - Znow;
0289     int sizeOld = 0;
0290     int sizeNew = 0;
0291     Vec4 dirNow = pNow / pNow.pAbs();
0292     Rndm& rndm  = pythiaMain.rndm;
0293 
0294     // Drop rate of geometric series. (Deuterium has corrected nCollAvg.)
0295     double probMore = 1. - 1. / nCollAvg(Anow);
0296 
0297     // Loop over varying number of hit nucleons in target nucleus.
0298     for (int iColl = 1; iColl <= Anow; ++iColl) {
0299       if (iColl > 1 && rndm.flat() > probMore) break;
0300 
0301       // Pick incoming projectile: trivial for first subcollision, else ...
0302       int iProj    = iHad;
0303       int procType = 0;
0304 
0305       // ... find highest-pLongitudinal particle from latest subcollision.
0306       if (iColl > 1) {
0307         iProj = 0;
0308         double pMax = 0.;
0309         for (int i = sizeOld; i < sizeNew; ++i)
0310         if ( eventMain[i].isFinal() && eventMain[i].isHadron()) {
0311           double pp = dot3(dirNow, eventMain[i].p());
0312           if (pp > pMax) {
0313             iProj = i;
0314             pMax  = pp;
0315           }
0316         }
0317 
0318         // No further subcollision if no particle with enough energy.
0319         if ( iProj == 0 || eventMain[iProj].e() - eventMain[iProj].m()
0320           < eKinMin) break;
0321 
0322         // Choose process; only SD or ND at perturbative energies.
0323         double eCMSub = (eventMain[iProj].p() + Vec4(0, 0, 0, mp)).mCalc();
0324         if (eCMSub > 10.) procType = (rndm.flat() < probSD) ? 4 : 1;
0325       }
0326 
0327       // Pick one p or n from target.
0328       int idProj = eventMain[iProj].id();
0329       bool doProton = rndm.flat() < (np / double(np + nn));
0330       if (doProton) np -= 1;
0331       else          nn -= 1;
0332       int idNuc = (doProton) ? 2212 : 2112;
0333 
0334       // Do a projectile-nucleon subcollision. Return empty event if failure.
0335       pythiaColl.setBeamIDs(idProj, idNuc);
0336       pythiaColl.setKinematics(eventMain[iProj].p(), Vec4());
0337       if (!pythiaColl.next(procType)) {
0338         eventMain.clear();
0339         return eventMain;
0340       }
0341 
0342       // Insert target nucleon. Mothers are (0,iProj) to mark who it
0343       // interacted with. Always use proton mass for simplicity.
0344       int statusNuc = (iColl == 1) ? -181 : -182;
0345       int iNuc = eventMain.append( idNuc, statusNuc, 0, iProj, 0, 0, 0, 0,
0346         0., 0., 0., mp, mp);
0347       eventMain[iNuc].vProdAdd(vNow);
0348 
0349       // Update full energy of the event with the proton mass.
0350       eventMain[0].e( eventMain[0].e() + mp);
0351       eventMain[0].m( eventMain[0].p().mCalc() );
0352 
0353       // Insert secondary produced particles (but skip intermediate partons)
0354       // into main event record and shift to correct production vertex.
0355       sizeOld = eventMain.size();
0356       for (int iSub = 3; iSub < eventColl.size(); ++iSub) {
0357         if (!eventColl[iSub].isFinal()) continue;
0358         int iNew = eventMain.append(eventColl[iSub]);
0359         eventMain[iNew].mothers(iNuc, iProj);
0360         eventMain[iNew].vProdAdd(vNow);
0361       }
0362       sizeNew = eventMain.size();
0363 
0364       // Update daughters of colliding hadrons and other history.
0365       eventMain[iProj].daughters(sizeOld, sizeNew - 1);
0366       eventMain[iNuc].daughters(sizeOld, sizeNew - 1);
0367       eventMain[iProj].statusNeg();
0368       eventMain[iProj].tau(0.);
0369 
0370     // End of loop over interactions in a nucleus.
0371     }
0372 
0373     // Optionally do decays of short-lived particles.
0374     if (rapidDecays) pythiaMain.moreDecays();
0375 
0376     // Optionally compress event record.
0377     if (listFinal) compress();
0378 
0379     // Return generated collision.
0380     return eventMain;
0381 
0382   }
0383 
0384   //--------------------------------------------------------------------------
0385 
0386   // Generate a particle decay, and return the event record.
0387   // You can allow sequential decays, if they occur rapidly enough.
0388 
0389   Event& nextDecay(int idNowIn, Vec4 pNowIn, double mNowIn,
0390     Vec4 vNow = Vec4() ) {
0391 
0392     // Save incoming quantities. (Not needed, but by analogy with
0393     // collisions.)
0394     idNow = idNowIn;
0395     pNow  = pNowIn;
0396     mNow  = mNowIn;
0397 
0398     // References to the event records. Clear them.
0399     Event& eventMain = pythiaMain.event;
0400     Event& eventColl = pythiaColl.event;
0401     eventMain.clear();
0402     eventColl.clear();
0403 
0404     // Insert incoming particle in cleared collision event record.
0405     eventColl.append(90,   -11, 0, 0, 1, 1, 0, 0, pNow, mNow);
0406     int iHad = eventColl.append(idNow, 12, 0, 0, 0, 0, 0, 0, pNow, mNow);
0407     eventColl[iHad].vProd(vNow);
0408 
0409     // Decay incoming particle. Return empty event if fail. Copy event record.
0410     if (!pythiaColl.moreDecays(iHad)) return eventMain;
0411     eventMain = eventColl;
0412 
0413     // Optionally do secondary decays of short-lived particles.
0414     if (rapidDecays) pythiaMain.moreDecays();
0415 
0416     // Optionally compress event record.
0417     if (listFinal) compress();
0418 
0419     // Return generated collision.
0420     return eventMain;
0421 
0422   }
0423 
0424   //--------------------------------------------------------------------------
0425 
0426   // Compress the event record by removing initial and intermediate
0427   // particles.  Keep line 0, since the += operator for event records
0428   // only adds from 1 on.
0429 
0430   void compress() {
0431 
0432     // Reference to the main event record. Original and new size.
0433     Event& eventMain = pythiaMain.event;
0434     int sizeOld = eventMain.size();
0435     int sizeNew = 1;
0436 
0437     // Loop through all particles and move up the final ones to the top.
0438     // Remove history information. Update event record size.
0439     for (int i = 1; i < sizeOld; ++i) if (eventMain[i].isFinal()) {
0440       eventMain[sizeNew] = eventMain[i];
0441       eventMain[sizeNew].mothers( 0, 0);
0442       eventMain[sizeNew].daughters( 0, 0);
0443       ++sizeNew;
0444     }
0445 
0446     // Shrink event record to new size.
0447     eventMain.popBack( sizeOld - sizeNew);
0448 
0449   }
0450 
0451   //--------------------------------------------------------------------------
0452 
0453   // Summary of aborts, errors and warnings.
0454 
0455   void stat() {
0456     pythiaMain.stat();
0457     pythiaColl.stat();
0458     logger.errorStatistics();
0459   }
0460 
0461   //--------------------------------------------------------------------------
0462 
0463   // Possibility to access particle data and random numbers from
0464   // pythiaMain.
0465 
0466   ParticleData& particleData() {return pythiaMain.particleData;}
0467 
0468   Rndm& rndm() {return pythiaMain.rndm;}
0469 
0470 //--------------------------------------------------------------------------
0471 
0472 private:
0473 
0474   // The Pythia instances used for decays and for collisions. Could
0475   // be made public, but cleaner to allow only limited access as
0476   // above.
0477   Pythia pythiaMain, pythiaColl;
0478 
0479   // Logger instance for errors in this class.
0480   Logger logger;
0481 
0482   // Save quantities.
0483   bool   listFinal, rapidDecays;
0484   int    idNow;
0485   double eMax, smallTau0, mp, mNow, eCMNow, sigmaNow;
0486   Vec4   pNow;
0487 
0488   // Fraction of single diffractive events beyond first collision in nucleus.
0489   static constexpr double probSD = 0.3;
0490 
0491   // Hadrons below the kinetic energy threshold cannot interact with medium.
0492   // (At least not using Pythia.) Used both in lab and in CM frame.
0493   static constexpr double eKinMin = 0.2;
0494 
0495   // Studied nuclei by A number, with offset and slope of <nColl>(sigma):
0496   // 1H, 2H, 4He, 9Be, 12C, 14N, 16O, 27Al, 40Ar, 56Fe, 63Cu, 84Kr,
0497   // 107Ag, 129Xe, 197Au, 208Pb.
0498   static constexpr int nA = 16;
0499   static constexpr double tabBorder = 20.;
0500   static const double tabA[nA];
0501   static const double tabOffset[nA];
0502   static const double tabSlope[nA];
0503   static const double tabSlopeLo[nA];
0504 
0505 };
0506 
0507 // Constant definitions.
0508 const double PythiaCascade::tabA[] = {1, 2, 4, 9, 12, 14, 16, 27, 40, 56,
0509   63, 84, 107, 129, 197, 208};
0510 const double PythiaCascade::tabOffset[] = {0., 0.03, 0.08, 0.15, 0.20, 0.20,
0511   0.20, 0.26, 0.30, 0.34, 0.40, 0.40, 0.40, 0.50, 0.50, 0.60};
0512 const double PythiaCascade::tabSlope[] = {0., 0.0016, 0.0033, 0.0075, 0.0092,
0513   0.0105, 0.012, 0.017, 0.022, 0.027, 0.028, 0.034, 0.040, 0.044, 0.055,
0514   0.055};
0515 const double PythiaCascade::tabSlopeLo[] = {0., 0.0031, 0.0073, 0.015, 0.0192,
0516   0.0205, 0.022, 0.03, 0.037, 0.044, 0.048, 0.054, 0.06, 0.069, 0.08, 0.085};
0517 
0518 //==========================================================================
0519 
0520 } // end namespace Pythia8
0521 
0522 #endif // end Pythia8_PythiaCascade_H