From 265502c7b38439e6b5aec03dd890d881cf657943 Mon Sep 17 00:00:00 2001 From: Felix Riehn <friehn@lip.pt> Date: Fri, 7 Mar 2025 12:49:32 +0000 Subject: [PATCH] Resolve "update pythia to v8.313" --- applications/c8_air_shower.cpp | 6 +- .../detail/modules/pythia8/Interaction.inl | 446 ------------------ .../modules/pythia8/InteractionModel.inl | 343 ++++++++++++++ .../modules/qgsjetII/InteractionModel.inl | 8 + .../modules/sibyll/InteractionModel.inl | 13 + .../framework/utility/CrossSectionTable.hpp | 89 ++++ corsika/modules/Pythia8.hpp | 15 + corsika/modules/pythia8/Interaction.hpp | 108 ----- corsika/modules/pythia8/InteractionModel.hpp | 153 ++++++ corsika/modules/qgsjetII/InteractionModel.hpp | 17 + corsika/modules/sibyll/InteractionModel.hpp | 3 + examples/CMakeLists.txt | 8 + examples/physics_examples/cross_section.cpp | 163 +++++++ .../physics_examples/had_interactions.cpp | 267 +++++++++++ modules/data | 2 +- modules/pythia8/CMakeLists.txt | 4 +- tests/modules/testPythia8.cpp | 4 +- tests/modules/testPythia8Interaction.inl | 96 ++-- 18 files changed, 1148 insertions(+), 597 deletions(-) delete mode 100644 corsika/detail/modules/pythia8/Interaction.inl create mode 100644 corsika/detail/modules/pythia8/InteractionModel.inl create mode 100644 corsika/framework/utility/CrossSectionTable.hpp delete mode 100644 corsika/modules/pythia8/Interaction.hpp create mode 100644 corsika/modules/pythia8/InteractionModel.hpp create mode 100644 examples/physics_examples/cross_section.cpp create mode 100644 examples/physics_examples/had_interactions.cpp diff --git a/applications/c8_air_shower.cpp b/applications/c8_air_shower.cpp index f48c48c40..1630426d3 100644 --- a/applications/c8_air_shower.cpp +++ b/applications/c8_air_shower.cpp @@ -266,7 +266,7 @@ int main(int argc, char** argv) { ->group("Misc."); app.add_option("-M,--hadronModel", "High-energy hadronic interaction model") ->default_val("SIBYLL-2.3d") - ->check(CLI::IsMember({"SIBYLL-2.3d", "QGSJet-II.04", "EPOS-LHC"})) + ->check(CLI::IsMember({"SIBYLL-2.3d", "QGSJet-II.04", "EPOS-LHC", "Pythia8"})) ->group("Misc."); app.add_option("-T,--hadronModelTransitionEnergy", "Transition between high-/low-energy hadronic interaction " @@ -440,6 +440,10 @@ int main(int argc, char** argv) { } else if (modelStr == "EPOS-LHC") { heModel = DynamicInteractionProcess<StackType>{ std::make_shared<corsika::epos::Interaction>(corsika::setup::C7trackedParticles)}; + } else if (modelStr == "Pythia8") { + heModel = DynamicInteractionProcess<StackType>{ + std::make_shared<corsika::pythia8::Interaction>( + corsika::setup::C7trackedParticles)}; } else { CORSIKA_LOG_CRITICAL("invalid choice \"{}\"; also check argument parser", modelStr); return EXIT_FAILURE; diff --git a/corsika/detail/modules/pythia8/Interaction.inl b/corsika/detail/modules/pythia8/Interaction.inl deleted file mode 100644 index 6c19a6c5b..000000000 --- a/corsika/detail/modules/pythia8/Interaction.inl +++ /dev/null @@ -1,446 +0,0 @@ -/* - * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu - * - * This software is distributed under the terms of the 3-clause BSD license. - * See file LICENSE for a full version of the license. - */ - -#pragma once - -#include <tuple> -#include <stdexcept> - -#include <boost/filesystem/path.hpp> -#include <fmt/core.h> - -#include <corsika/modules/pythia8/Interaction.hpp> - -#include <corsika/framework/geometry/FourVector.hpp> -#include <corsika/framework/utility/COMBoost.hpp> -#include <corsika/framework/core/EnergyMomentumOperations.hpp> -#include <corsika/media/Environment.hpp> -#include <corsika/media/NuclearComposition.hpp> - -namespace corsika::pythia8 { - - inline Interaction::~Interaction() { - CORSIKA_LOG_INFO("Pythia::Interaction n= {}", count_); - } - - inline Interaction::Interaction(boost::filesystem::path const& mpiInitFile, - bool const print_listing) - : print_listing_(print_listing) - , pythiaMain_{CORSIKA_Pythia8_XML_DIR, false} - , pythiaColl_{CORSIKA_Pythia8_XML_DIR, false} { - auto rndm = std::make_shared<corsika::pythia8::Random>(); - pythiaColl_.setRndmEnginePtr(rndm); - pythiaMain_.setRndmEnginePtr(rndm); - - CORSIKA_LOG_INFO("Pythia8 MPI init file: {}", mpiInitFile.native()); - // Main Pythia object for managing the cascade evolution. - // Can also do decays, but no hard processes. - - pythiaMain_.readString("ProcessLevel:all = off"); - - // Reduce statistics printout to relevant ones. - pythiaMain_.readString("Stat:showProcessLevel = off"); - pythiaMain_.readString("Stat:showPartonLevel = off"); - - // Add Argon, since not in Particle data. id:all = name antiName - // spinType chargeType colType m0 mWidth mMin mMax tau0. - pythiaMain_.readString( - "1000180400:all = 40Ar 40Arbar 1 54 0 " - "37.22474 0. 0. 0. 0."); - - // we can't test this block, LCOV_EXCL_START - if (!pythiaMain_.init()) - throw std::runtime_error("Pythia::Interaction: Initialization failed!"); - // LCOV_EXCL_STOP - - // Secondary Pythia object for performing individual collisions. - // Variable incoming beam type and energy. - pythiaColl_.readString("Beams:allowVariableEnergy = on"); - pythiaColl_.readString("Beams:allowIDAswitch = on"); - - // Set up for fixed-target collisions. - pythiaColl_.readString( - "Beams:frameType = 3"); // arbitrary frame, need to define full 4-momenta - pythiaColl_.settings.parm("Beams:pzA", eMaxLab_ / 1_GeV); - pythiaColl_.readString("Beams:pzB = 0."); - - // Must use the soft and low-energy QCD processes. - pythiaColl_.readString("SoftQCD:all = on"); - pythiaColl_.readString("LowEnergyQCD:all = on"); - - // Decays to be done by pythiaMain_. - pythiaColl_.readString("HadronLevel:Decay = off"); - - // Reduce printout and relax energy-momentum conservation. - pythiaColl_.readString("Print:quiet = on"); - pythiaColl_.readString("Check:epTolErr = 0.1"); - pythiaColl_.readString("Check:epTolWarn = 0.0001"); - pythiaColl_.readString("Check:mTolErr = 0.01"); - - // Redure statistics printout to relevant ones. - pythiaColl_.readString("Stat:showProcessLevel = off"); - pythiaColl_.readString("Stat:showPartonLevel = off"); - - bool const reuse = true; // could be made more flexible - // Reuse MPI initialization file if it exists; else create a new one. - if (reuse) - pythiaColl_.readString("MultipartonInteractions:reuseInit = 3"); - else - pythiaColl_.readString("MultipartonInteractions:reuseInit = 1"); - pythiaColl_.settings.word("MultipartonInteractions:initFile", mpiInitFile.native()); - - // initialize - // we can't test this block, LCOV_EXCL_START - if (!pythiaColl_.init()) - throw std::runtime_error("Pythia::Interaction: Initialization failed!"); - // LCOV_EXCL_STOP - } - - inline bool Interaction::isValid(Code const projectileId, Code const targetId, - HEPEnergyType const sqrtS) const { - if (is_nucleus(projectileId)) // not yet possible with Pythia - return false; - - HEPEnergyType const labE = calculate_lab_energy( - static_pow<2>(sqrtS), get_mass(projectileId), get_mass(targetId)); - if (labE < eKinMinLab_) return false; - - return std::find(validTargets_.begin(), validTargets_.end(), targetId) != - validTargets_.end(); - } - - inline bool Interaction::canInteract(Code const pCode) const { - return is_hadron(pCode) && !is_nucleus(pCode); // should be sufficient - } - - inline std::tuple<CrossSectionType, CrossSectionType> - Interaction::getCrossSectionInelEla(Code const projectileId, Code const targetId, - FourMomentum const& projectileP4, - FourMomentum const& targetP4) const { - HEPEnergyType const CoMenergy = - is_nucleus(targetId) - ? (projectileP4 + targetP4 / get_nucleus_A(targetId)).getNorm() - : (projectileP4 + targetP4).getNorm(); - - if (is_nucleus(targetId) || is_nucleus(projectileId)) { - CORSIKA_LOG_ERROR( - "Pythia8::Interaction::getCrossSectionInelEla() called with nuclear projectile " - "or target"); - return std::make_tuple(CrossSectionType::zero(), CrossSectionType::zero()); - } - - if (!isValid(projectileId, targetId, CoMenergy)) { - return std::make_tuple(CrossSectionType::zero(), CrossSectionType::zero()); - } - - // input particle PDG - auto const pdgCodeBeam = static_cast<int>(get_PDG(projectileId)); - auto const pdgCodeTarget = static_cast<int>(get_PDG(targetId)); - double const ecm_GeV = CoMenergy * (1 / 1_GeV); - - auto const sigTot = - pythiaColl_.getSigmaTotal(pdgCodeBeam, pdgCodeTarget, ecm_GeV) * 1_mb; - auto const sigEla = - pythiaColl_.getSigmaPartial(pdgCodeBeam, pdgCodeTarget, ecm_GeV, 2) * 1_mb; - - return std::make_tuple(sigTot - sigEla, sigEla); - } - - CrossSectionType Interaction::getCrossSection(Code const projectileId, - Code const targetId, - FourMomentum const& projectileP4, - FourMomentum const& targetP4) const { - - if (!is_nucleus(targetId)) { - auto const [sigProd, sigEla] = - getCrossSectionInelEla(projectileId, targetId, projectileP4, targetP4); - return sigProd + sigEla; - } else if (get_nucleus_A(targetId) == 1) { - auto const [sigProd, sigEla] = getCrossSectionInelEla( - projectileId, get_nucleus_Z(targetId) == 1 ? Code::Proton : Code::Neutron, - projectileP4, targetP4); - return sigProd + sigEla; - } else { - auto const [sigProd, sigEla] = - getCrossSectionInelEla(projectileId, Code::Proton, projectileP4, targetP4); - auto const sigTot = sigProd + sigEla; - auto const nSubcoll = getAverageSubcollisions(targetId, sigTot); - if (nSubcoll == 0) { // no parameterization available -> we can't handle this - return CrossSectionType::zero(); - } else { - return sigTot * get_nucleus_A(targetId) / nSubcoll; - } - } - } - - double Interaction::getAverageSubcollisions(Code targetId, - CrossSectionType sigTot) const { - if (targetId == Code::Proton || targetId == Code::Neutron || - targetId == Code::Hydrogen || targetId == Code::AntiProton || - targetId == Code::AntiNeutron) - return 1; - - auto const Z = get_nucleus_Z(targetId); - auto const A = get_nucleus_A(targetId); - - double nCollAvg; - - if (Z == 7 && A == 14) - nCollAvg = (sigTot < 31._mb) // this is from the paper - ? 1. + 0.017 / 1_mb * sigTot - : 1.2 + 0.0105 / 1_mb * sigTot; - else if (Z == 8 && A == 16) - nCollAvg = (sigTot < 16._mb) // provided by Torbjörn Sjöstrand - ? 1. + 0.0245 / 1_mb * sigTot - : 1.2 + 0.012 / 1_mb * sigTot; - else if (Z == 18 && A == 40) - nCollAvg = - (sigTot < 10._mb) ? 1. + 0.050 / 1_mb * sigTot : 1.28 + 0.022 / 1_mb * sigTot; - else { - CORSIKA_LOG_ERROR( - fmt::format("Pythia8 cross-sections not defined for ({},{}) nucleus", A, Z)); - nCollAvg = 0; - } - - return nCollAvg; - } - - template <class TView> - inline void Interaction::doInteraction(TView& view, Code const projectileId, - Code const targetId, - FourMomentum const& projectileP4, - FourMomentum const& targetP4) { - - CORSIKA_LOG_DEBUG( - "Pythia::Interaction: " - "doInteraction: {} interaction? ", - projectileId, corsika::pythia8::Interaction::canInteract(projectileId)); - - // define system - auto const sqrtS2 = (projectileP4 + targetP4).getNormSqr(); - HEPEnergyType const sqrtS = sqrt(sqrtS2); - HEPEnergyType const eProjectileLab = (sqrtS2 - static_pow<2>(get_mass(projectileId)) - - static_pow<2>(get_mass(targetId))) / - (2 * get_mass(targetId)); - - if (!isValid(projectileId, targetId, sqrtS)) { - throw std::runtime_error("invalid target,projectile,energy combination."); - } - - CORSIKA_LOG_DEBUG("Interaction: ebeam lab: {} GeV", eProjectileLab / 1_GeV); - CORSIKA_LOG_DEBUG( - "Interaction: " - " doInteraction: E(GeV): {}" - " Ecm(GeV): {}", - eProjectileLab / 1_GeV, sqrtS / 1_GeV); - - count_++; - - int const idNow = static_cast<int>(get_PDG(projectileId)); - - // References to the two event records. Clear main event record. - Pythia8::Event& eventMain = pythiaMain_.event; - Pythia8::Event& eventColl = pythiaColl_.event; - - COMBoost const labFrameBoost{targetP4.getSpaceLikeComponents(), get_mass(targetId)}; - auto const proj4MomLab = labFrameBoost.toCoM(projectileP4); - auto const& rotCS = labFrameBoost.getRotatedCS(); - auto const pProjLab = proj4MomLab.getSpaceLikeComponents().getComponents(rotCS); - - auto constexpr invGeV = 1 / 1_GeV; - - Pythia8::Vec4 const pNow{pProjLab.getX() * invGeV, pProjLab.getY() * invGeV, - pProjLab.getZ() * invGeV, - proj4MomLab.getTimeLikeComponent() * invGeV}; - - // Insert incoming particle in cleared main event record. - int unsuccessful_iterations{0}; - do { // retry event generation if Pythia gets stuck - eventMain.clear(); - eventMain.append(90, -11, 0, 0, 1, 1, 0, 0, pNow, pNow.mCalc()); - int const iHad = eventMain.append(idNow, 12, 0, 0, 0, 0, 0, 0, pNow, - get_mass(projectileId) * (1 / 1_GeV)); - - Pythia8::Vec4 const vNow{}; // production vertex - - eventMain[iHad].vProd(vNow); - - auto const [Anow, Znow] = std::invoke([targetId]() { - if (targetId == Code::Proton) { - return std::make_pair(1, 1); - } else if (targetId == Code::Neutron) { - return std::make_pair(1, 0); - } else if (is_nucleus(targetId)) { - return std::make_pair<int, int>(get_nucleus_A(targetId), - get_nucleus_Z(targetId)); - } else { - // due to the earlier call to isValid(), we shouldn't end up here - // LCOV_EXCL_START - CORSIKA_LOG_ERROR("invalid target {}; you shouldn't have gotten this far!", - targetId); - return std::make_pair(0, 0); - // LCOV_EXCL_STOP - } - }); - - // Set up for collisions on a nucleus. - int np = Znow; - int nn = Anow - Znow; - int sizeOld = 0; - int sizeNew = 0; - Pythia8::Vec4 const dirNow = pNow / pNow.pAbs(); - Pythia8::Rndm& rndm = pythiaMain_.rndm; - - double constexpr mp = get_mass(Code::Proton) / 1_GeV; - double const sqrtSNN_GeV = (pNow + Pythia8::Vec4{0, 0, 0, mp}).mCalc(); - - auto const nCollAvg = getAverageSubcollisions( - targetId, pythiaColl_.getSigmaTotal(idNow, 2212, sqrtSNN_GeV) * 1_mb); - double const probMore = 1. - 1. / nCollAvg; - - // Loop over varying number of hit nucleons in target nucleus. - for (int iColl = 1; iColl <= Anow; ++iColl) { - if (iColl > 1 && rndm.flat() > probMore) break; - - // Pick incoming projectile: trivial for first subcollision, else ... - int iProj = iHad; - int procType = 0; - - // ... find highest-pLongitudinal particle from latest subcollision. - if (iColl > 1) { - iProj = 0; - double pMax = 0.; - for (int i = sizeOld; i < sizeNew; ++i) - if (eventMain[i].isFinal() && eventMain[i].isHadron()) { - if (double const pp = Pythia8::dot3(dirNow, eventMain[i].p()); pp > pMax) { - iProj = i; - pMax = pp; - } - } - - // No further subcollision if no particle with enough energy. - // cannot be reliably provoked in tests - // LCOV_EXCL_START - if (iProj == 0 || - eventMain[iProj].e() - eventMain[iProj].m() < eKinMinLab_ / 1_GeV) - break; - // LCOV_EXCL_STOP - - // Choose process; only SD or ND at perturbative energies. - double const eCMSub = - (eventMain[iProj].p() + Pythia8::Vec4{0, 0, 0, mp}).mCalc(); - if (eCMSub > 10.) procType = (rndm.flat() < probSD_) ? 4 : 1; - } - - // Pick one p or n from target. - int const idProj = eventMain[iProj].id(); - bool const doProton = rndm.flat() < (np / double(np + nn)); - if (doProton) - np--; - else - nn--; - int const idNuc = doProton ? 2212 : 2112; - - // Perform the projectile-nucleon subcollision. - pythiaColl_.setBeamIDs(idProj, idNuc); - pythiaColl_.setKinematics(eventMain[iProj].p(), Pythia8::Vec4()); - - if (!pythiaColl_.next(procType)) { - CORSIKA_LOG_WARN("Pythia collision next() failed {} {} {}", - eventMain[iProj].p(), idProj, idNuc); - eventMain.clear(); - ++unsuccessful_iterations; - goto event_repeat; // retry event, last remaining good use-case for goto - } - - // Insert target nucleon. Mothers are (0,iProj) to mark who it - // interacted with. Always use proton mass for simplicity. - int const statusNuc = (iColl == 1) ? -181 : -182; - int const iNuc = - eventMain.append(idNuc, statusNuc, 0, iProj, 0, 0, 0, 0, 0., 0., 0., mp, mp); - eventMain[iNuc].vProdAdd(vNow); - - // Update full energy of the event with the proton mass. - eventMain[0].e(eventMain[0].e() + mp); - eventMain[0].m(eventMain[0].p().mCalc()); - - // Insert secondary produced particles (but skip intermediate partons) - // into main event record and shift to correct production vertex. - sizeOld = eventMain.size(); - for (int iSub = 3; iSub < eventColl.size(); ++iSub) { - if (!eventColl[iSub].isFinal()) continue; - int const iNew = eventMain.append(eventColl[iSub]); - eventMain[iNew].mothers(iNuc, iProj); - eventMain[iNew].vProdAdd(vNow); - } - sizeNew = eventMain.size(); - - // Update daughters of colliding hadrons and other history. - eventMain[iProj].daughters(sizeOld, sizeNew - 1); - eventMain[iNuc].daughters(sizeOld, sizeNew - 1); - eventMain[iProj].statusNeg(); - double dTau = (iColl == 1) ? (vNow.e() - eventMain[iHad].tProd()) * - eventMain[iHad].m() / eventMain[iHad].e() - : 0.; - eventMain[iProj].tau(dTau); - - // End of loop over interactions in a nucleus. - } - break; // - - event_repeat:; - } while (unsuccessful_iterations < 100); - - if (unsuccessful_iterations >= 100) { - CORSIKA_LOG_CRITICAL( - "Pythia event generation failed after 100 trials: projectile: {}, target: {}", - projectileId, targetId); - throw std::runtime_error{"Pythia event generation failed after 100 trials"}; - } - - MomentumVector Plab_final{labFrameBoost.getOriginalCS()}; - auto Elab_final = HEPEnergyType::zero(); - - for (Pythia8::Particle const& p8p : eventMain) { - // skip particles that have decayed / are initial particles in pythia's event record - if (!p8p.isFinal()) continue; - try { - auto const volatile id = static_cast<PDGCode>(p8p.id()); - auto const pyId = convert_from_PDG(id); - - MomentumVector const pyPlab( - rotCS, {p8p.px() * 1_GeV, p8p.py() * 1_GeV, p8p.pz() * 1_GeV}); - auto const pyP = labFrameBoost.fromCoM(FourVector{p8p.e() * 1_GeV, pyPlab}); - - HEPEnergyType const mass = get_mass(pyId); - HEPEnergyType const Ekin = - sqrt(pyP.getSpaceLikeComponents().getSquaredNorm() + mass * mass) - mass; - - // add to corsika stack - auto pnew = view.addSecondary(std::make_tuple(pyId, Ekin, pyPlab.normalized())); - - Plab_final += pnew.getMomentum(); - Elab_final += pnew.getEnergy(); - } - // irreproducible in tests, LCOV_EXCL_START - catch (std::out_of_range const& ex) { - CORSIKA_LOG_CRITICAL("Pythia ID {} unknown in C8", p8p.id()); - throw ex; - } - // LCOV_EXCL_STOP - } - - eventMain.clear(); - - CORSIKA_LOG_DEBUG( - "conservation (all GeV): " - "Elab_final= {}" - ", Plab_final= {}", - Elab_final / 1_GeV, (Plab_final / 1_GeV).getComponents()); - } - -} // namespace corsika::pythia8 diff --git a/corsika/detail/modules/pythia8/InteractionModel.inl b/corsika/detail/modules/pythia8/InteractionModel.inl new file mode 100644 index 000000000..01adc3c41 --- /dev/null +++ b/corsika/detail/modules/pythia8/InteractionModel.inl @@ -0,0 +1,343 @@ +/* + * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu + * + * This software is distributed under the terms of the 3-clause BSD license. + * See file LICENSE for a full version of the license. + */ + +#pragma once + +#include <utility> +#include <stdexcept> + +#include <boost/filesystem/path.hpp> +#include <boost/iterator/transform_iterator.hpp> +#include <fmt/core.h> +#include <cnpy.hpp> + +#include <corsika/modules/pythia8/InteractionModel.hpp> + +#include <corsika/framework/geometry/FourVector.hpp> +#include <corsika/framework/utility/COMBoost.hpp> +#include <corsika/framework/utility/CrossSectionTable.hpp> +#include <corsika/framework/core/EnergyMomentumOperations.hpp> +#include <corsika/media/Environment.hpp> +#include <corsika/media/NuclearComposition.hpp> + +namespace corsika::pythia8 { + + inline InteractionModel::~InteractionModel() {} + + inline InteractionModel::InteractionModel(std::set<Code> const& stableParticles, + boost::filesystem::path const& dataPath, + bool const printListing) + : crossSectionPPElastic_{loadPPTable(dataPath, "sig_el")} + , crossSectionPPInelastic_{loadPPTable(dataPath, "sig_inel")} + , print_listing_(printListing) { + auto rndm = std::make_shared<corsika::pythia8::Random>(); + pythia_.setRndmEnginePtr(rndm); + + CORSIKA_LOG_INFO("Pythia8 reuse files: {}", dataPath.native()); + + // projectile and target init to p Nitrogen to initialize pythia with angantyr + pythia_.readString("Beams:allowIDASwitch = on"); + pythia_.settings.mode("Beams:idA", + static_cast<PDGCodeIntType>(get_PDG(Code::Proton))); + pythia_.settings.mode("Beams:idB", + static_cast<PDGCodeIntType>(get_PDG(Code::Oxygen))); + + pythia_.readString("Beams:allowVariableEnergy = on"); + pythia_.readString("Beams:frameType = 1"); // CoM + // initialize a maximum energy event + pythia_.settings.parm("Beams:eCM", 400_TeV / 1_GeV); + + // needed when regular Pythia (not Angantyr) takes over for pp + pythia_.readString("SoftQCD:all = on"); + + /* reuse file settings + reuseInit = 2: initialization data is reuse file, but if the file is not found, + initialization fails reuseInit = 3: same, but if the file is not found, it will be + generated and saved after normal initialization */ + pythia_.readString("MultipartonInteractions:reuseInit = 2"); + pythia_.readFile(dataPath.native() + "/pythia8313.cmnd"); + // switch on decays for all hadrons except the ones defined as tracked by C8 + if (!stableParticles.empty()) { + pythia_.readString("HadronLevel:Decay = on"); + for (auto pCode : stableParticles) + pythia_.particleData.mayDecay(static_cast<int>(get_PDG(pCode)), false); + } else { + // all hadrons stable + pythia_.readString("HadronLevel:Decay = on"); + } + // Reduce printout and relax energy-momentum conservation. + pythia_.readString("Print:quiet = on"); + pythia_.readString("Check:epTolErr = 0.1"); + pythia_.readString("Check:epTolWarn = 0.0001"); + pythia_.readString("Check:mTolErr = 0.01"); + + // Reduce statistics printout to relevant ones. + pythia_.readString("Stat:showProcessLevel = off"); + pythia_.readString("Stat:showPartonLevel = off"); + + // initialize + // we can't test this block, LCOV_EXCL_START + if (!pythia_.init()) + throw std::runtime_error("Pythia::InteractionModel: Initialization failed!"); + // LCOV_EXCL_STOP + + // create/load tables of cross sections + for (Code const projectile : validProjectiles_) { + for (Code const target : validTargets_) { + if (xs_map_.find(projectile) == xs_map_.end()) { + // projectile not mapped, load table directly + auto const tablePath = + dataPath / "pythia8_xs_npz" / + fmt::format("xs_{}_{}.npz", + static_cast<PDGCodeIntType>(get_PDG(projectile)), + static_cast<PDGCodeIntType>(get_PDG(target))); + auto const tables = cnpy::npz_load(tablePath.native()); + // NOTE: tables are calculated for plab. In C8 we we assume elab. starting at + // plab=100GeV the difference is at most (1+m**2/p**2)=1.000088035 + auto const energies = tables.at("plab").as_vec<float>(); + auto const total_xs = tables.at("sig_tot").as_vec<float>(); + + if (auto const e_size = energies.size(), xs_size = total_xs.size(); + xs_size != e_size) { + throw std::runtime_error{ + fmt::format("Pythia8 table corrupt (plab size = {}; sig_tot size = {})", + e_size, xs_size)}; + } + + auto xs_it = boost::make_transform_iterator(total_xs.cbegin(), millibarn_mult); + auto e_it_beg = boost::make_transform_iterator(energies.cbegin(), GeV_mult); + decltype(e_it_beg) e_it_end = + boost::make_transform_iterator(energies.cend(), GeV_mult); + crossSectionTables_.insert( + {std::pair{projectile, target}, + CrossSectionTable<InterpolationTransforms::Log>{ + std::move(e_it_beg), std::move(e_it_end), std::move(xs_it)}}); + } + } + } + } + + inline CrossSectionTable<InterpolationTransforms::Log> InteractionModel::loadPPTable( + boost::filesystem::path const& dataPath, char const* key) { + auto const ppTablePath = + dataPath / "pythia8_xs_npz" / + fmt::format("xs_{}_{}.npz", static_cast<PDGCodeIntType>(get_PDG(Code::Proton)), + static_cast<PDGCodeIntType>(get_PDG(Code::Proton))); + auto const tables = cnpy::npz_load(ppTablePath.native()); + // NOTE: tables are calculated for plab. In C8 we we assume elab. starting at + // plab=100GeV the difference is at most (1+m**2/p**2)=1.000088035 + auto const energies = tables.at("plab").as_vec<float>(); + auto const xs = tables.at(key).as_vec<float>(); + + if (auto e_size = energies.size(), xs_size = xs.size(); xs_size != e_size) { + throw std::runtime_error{fmt::format( + "Pythia8 table corrupt (plab size = {}; xs size = {})", e_size, xs_size)}; + } + + auto xs_it = boost::make_transform_iterator(xs.cbegin(), millibarn_mult); + auto e_it_beg = boost::make_transform_iterator(energies.cbegin(), GeV_mult); + decltype(e_it_beg) e_it_end = + boost::make_transform_iterator(energies.cend(), GeV_mult); + return CrossSectionTable<InterpolationTransforms::Log>{ + std::move(e_it_beg), std::move(e_it_end), std::move(xs_it)}; + } + + inline bool InteractionModel::isValid(Code const projectileId, Code const targetId, + HEPEnergyType const sqrtSNN) const { + + CORSIKA_LOG_DEBUG("pythia isValid: {} + {} at sqrtSNN = {} GeV", projectileId, + targetId, sqrtSNN / 1_GeV); + if (is_nucleus(targetId)) + CORSIKA_LOG_DEBUG("nucleus = {}-{}", get_nucleus_A(targetId), + get_nucleus_Z(targetId)); + if (is_nucleus(projectileId)) // not yet possible with Pythia + return false; + + if (!canInteract(projectileId)) return false; + + auto const mass_target = + get_mass(targetId) / (is_nucleus(targetId) ? get_nucleus_A(targetId) : 1.); + HEPEnergyType const labE = + calculate_lab_energy(static_pow<2>(sqrtSNN), get_mass(projectileId), mass_target); + if (labE < eKinMinLab_) return false; + + bool const validProjectile = + std::find(validProjectiles_.begin(), validProjectiles_.end(), projectileId) != + validProjectiles_.end(); + bool const validTarget = std::find(validTargets_.begin(), validTargets_.end(), + targetId) != validTargets_.end(); + return validProjectile && validTarget; + } + + inline bool InteractionModel::canInteract(Code const pCode) const { + return is_hadron(pCode) && !is_nucleus(pCode); + } + + inline std::tuple<CrossSectionType, CrossSectionType> + InteractionModel::getCrossSectionInelEla(Code const projectileId, Code const targetId, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) const { + // elastic cross sections only available for proton + if (projectileId != Code::Proton || targetId != Code::Proton) { + return std::tuple{CrossSectionType::zero(), CrossSectionType::zero()}; + } + + // NOTE: here we calculate SNN (per nucleon energy) AND S (per particle energy) + // because isValid expects sqrtSNN as input but the tables need Elab + auto const Aprojectile = + (is_nucleus(projectileId) ? get_nucleus_A(projectileId) : 1.); + auto const Atarget = (is_nucleus(targetId) ? get_nucleus_A(targetId) : 1.); + auto const SNN = (projectileP4 / Aprojectile + targetP4 / Atarget).getNormSqr(); + HEPEnergyType const sqrtSNN = sqrt(SNN); + if (!isValid(projectileId, targetId, sqrtSNN)) + return std::tuple{CrossSectionType::zero(), CrossSectionType::zero()}; + + // read cross section from table + // define system (the tables were created for lab. energies) since we cannot assume + // the input is in the lab. frame we calculate the lab. energy from SNN = + // (projectileP4 / Aprojectile + targetP4 / Atarget)**2 and Elab = S/(2. * mTarget / + // Atarget) where A* is the number of nucleons in a nucleus. A* is 1 if projectile or + // target are simple hadrons. + HEPEnergyType const Elab = calculate_lab_energy( + SNN, get_mass(projectileId) / Aprojectile, get_mass(targetId) / Atarget); + + CrossSectionType const xsEl = crossSectionPPElastic_.interpolate(Elab); + CrossSectionType const xsInel = crossSectionPPInelastic_.interpolate(Elab); + return std::pair{xsInel, xsEl}; + } + + inline CrossSectionType InteractionModel::getCrossSection( + Code const projectileId, Code const targetId, FourMomentum const& projectileP4, + FourMomentum const& targetP4) const { + + auto const Aprojectile = + (is_nucleus(projectileId) ? get_nucleus_A(projectileId) : 1.); + auto const Atarget = (is_nucleus(targetId) ? get_nucleus_A(targetId) : 1.); + + auto const SNN = (projectileP4 / Aprojectile + targetP4 / Atarget).getNormSqr(); + HEPEnergyType const sqrtSNN = sqrt(SNN); + if (!isValid(projectileId, targetId, sqrtSNN)) return CrossSectionType::zero(); + + // read cross section from table + // define system (the tables were created for lab. energies) since we cannot assume + // the input is in the lab. frame we calculate the lab. energy from SNN = + // (projectileP4 / Aprojectile + targetP4 / Atarget)**2 and Elab = S/(2. * mTarget / + // Atarget) where A* is the number of nucleons in a nucleus. A* is 1 if projectile or + // target are simple hadrons. + auto const it = xs_map_.find(projectileId); + auto const mapped_projectile = (it == xs_map_.end()) ? projectileId : it->second; + auto const& table = crossSectionTables_.at(std::pair{mapped_projectile, targetId}); + HEPEnergyType const Elab = calculate_lab_energy( + SNN, get_mass(projectileId) / Aprojectile, get_mass(targetId) / Atarget); + CORSIKA_LOG_DEBUG("pythia getCrossSection: {}+{} at Elab= {} GeV, sqrtSNN = {} GeV", + projectileId, get_name(targetId), Elab / 1_GeV, sqrtSNN / 1_GeV); + return table.interpolate(Elab); + } + + template <class TView> + inline void InteractionModel::doInteraction(TView& view, Code const projectileId, + Code const targetId, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) { + + CORSIKA_LOG_DEBUG( + "Pythia::InteractionModel: " + "doInteraction: {} interaction? {} ", + projectileId, corsika::pythia8::InteractionModel::canInteract(projectileId)); + + // define system nucleon-nucleon + auto const projectileP4NN = + projectileP4 / (is_nucleus(projectileId) ? get_nucleus_A(projectileId) : 1); + auto const targetP4NN = + targetP4 / (is_nucleus(targetId) ? get_nucleus_A(targetId) : 1); + auto const SNN = (projectileP4NN + targetP4NN).getNormSqr(); + auto const sqrtSNN = sqrt(SNN); + HEPEnergyType const eProjectileLab = calculate_lab_energy( + SNN, get_mass(projectileId), get_mass(targetId) / get_nucleus_A(targetId)); + + CORSIKA_LOG_DEBUG("InteractionModel: ebeam lab: {} GeV", eProjectileLab / 1_GeV); + CORSIKA_LOG_DEBUG( + "InteractionModel: " + " doInteraction: E(GeV): {}" + " Ecm_NN(GeV): {}", + eProjectileLab / 1_GeV, sqrtSNN / 1_GeV); + + if (!isValid(projectileId, targetId, sqrtSNN)) { + throw std::runtime_error("invalid target,projectile,energy combination."); + } + + COMBoost const boost{projectileP4NN, targetP4NN}; + auto const& rotCS = boost.getRotatedCS(); + + // variables to talk to Pythia + double const eCM_pythia = sqrtSNN / 1_GeV; // CoM energy hadron-Nucleon + double const idA_pythia = static_cast<double>(get_PDG(projectileId)); + double const idB_pythia = static_cast<double>(get_PDG(targetId)); + CORSIKA_LOG_DEBUG("re-configuring pythia idA={}, idB={}, ecm={} GeV", idA_pythia, + idB_pythia, eCM_pythia); + // configure event + pythia_.setBeamIDs(idA_pythia, idB_pythia); + pythia_.setKinematics(eCM_pythia); + + // generate event (one chance only!) + if (!pythia_.next()) { + throw std::runtime_error("Pythia::InteractionModel: interaction failed!"); + } + + // LCOV_EXCL_START, we don't validate pythia8 internals + if (print_listing_) { + // print final state + pythia_.event.list(); + } + // LCOV_EXCL_STOP + + MomentumVector Plab_final{boost.getOriginalCS()}; + auto Elab_final = HEPEnergyType::zero(); + + for (Pythia8::Particle const& p8p : pythia_.event) { + // skip particles that have decayed and initial particles + if (!p8p.isFinal()) continue; + try { + auto const volatile id = static_cast<PDGCode>(p8p.id()); + auto const pyId = convert_from_PDG(id); + + // skip nuclear remnants + if (is_nucleus(pyId)) continue; + + MomentumVector const pyPcom( + rotCS, {p8p.px() * 1_GeV, p8p.py() * 1_GeV, p8p.pz() * 1_GeV}); + auto const pyP = boost.fromCoM(FourVector{p8p.e() * 1_GeV, pyPcom}); + + HEPEnergyType const mass = get_mass(pyId); + HEPEnergyType const Ekin = + sqrt(pyP.getSpaceLikeComponents().getSquaredNorm() + mass * mass) - mass; + + // add to corsika stack + auto pnew = view.addSecondary( + std::make_tuple(pyId, Ekin, pyP.getSpaceLikeComponents().normalized())); + + Plab_final += pnew.getMomentum(); + Elab_final += pnew.getEnergy(); + } + // irreproducible in tests, LCOV_EXCL_START + catch (std::out_of_range const& ex) { + CORSIKA_LOG_CRITICAL("Pythia ID {} unknown in C8", p8p.id()); + throw ex; + } + // LCOV_EXCL_STOP + } + + pythia_.event.clear(); + + CORSIKA_LOG_DEBUG( + "conservation (all GeV): " + "Elab_final= {}" + ", Plab_final= {}", + Elab_final / 1_GeV, (Plab_final / 1_GeV).getComponents()); + } + +} // namespace corsika::pythia8 diff --git a/corsika/detail/modules/qgsjetII/InteractionModel.inl b/corsika/detail/modules/qgsjetII/InteractionModel.inl index 7ce791a07..232d0d392 100644 --- a/corsika/detail/modules/qgsjetII/InteractionModel.inl +++ b/corsika/detail/modules/qgsjetII/InteractionModel.inl @@ -101,6 +101,14 @@ namespace corsika::qgsjetII { return sigProd * 1_mb; } + inline std::tuple<CrossSectionType, CrossSectionType> + InteractionModel::getCrossSectionInelEla(Code projCode, Code targetCode, + FourMomentum const& proj4mom, + FourMomentum const& target4mom) const { + return {getCrossSection(projCode, targetCode, proj4mom, target4mom), + CrossSectionType::zero()}; + } + template <typename TSecondaries> inline void InteractionModel::doInteraction(TSecondaries& view, Code const projectileId, Code const targetId, diff --git a/corsika/detail/modules/sibyll/InteractionModel.inl b/corsika/detail/modules/sibyll/InteractionModel.inl index a3d8f3f06..979a97f29 100644 --- a/corsika/detail/modules/sibyll/InteractionModel.inl +++ b/corsika/detail/modules/sibyll/InteractionModel.inl @@ -50,6 +50,19 @@ namespace corsika::sibyll { target4mom); } + inline std::tuple<CrossSectionType, CrossSectionType> + InteractionModel::getCrossSectionInelEla(Code projCode, Code targetCode, + FourMomentum const& proj4mom, + FourMomentum const& target4mom) const { + if (is_nucleus(projCode)) + return {getNuclearInteractionModel().getCrossSection(projCode, targetCode, proj4mom, + target4mom), + CrossSectionType::zero()}; + else + return getHadronInteractionModel().getCrossSectionInelEla(projCode, targetCode, + proj4mom, target4mom); + } + template <typename TSecondaries> inline void InteractionModel::doInteraction(TSecondaries& view, Code projCode, Code targetCode, diff --git a/corsika/framework/utility/CrossSectionTable.hpp b/corsika/framework/utility/CrossSectionTable.hpp new file mode 100644 index 000000000..c32494429 --- /dev/null +++ b/corsika/framework/utility/CrossSectionTable.hpp @@ -0,0 +1,89 @@ +/* + * (c) Copyright 2024 CORSIKA Project, corsika-project@lists.kit.edu + * + * This software is distributed under the terms of the 3-clause BSD license. + * See file LICENSE for a full version of the license. + */ + +#pragma once + +#include <algorithm> +#include <cmath> +#include <stdexcept> +#include <utility> +#include <vector> + +#include <boost/functional/identity.hpp> +#include <boost/iterator/transform_iterator.hpp> + +#include <corsika/framework/core/PhysicalUnits.hpp> + +namespace corsika { + namespace InterpolationTransforms { + using Identity = boost::identity; + + struct Log { + template <typename T> + auto operator()(T val) const { + if constexpr (is_quantity_v<T>) { + return std::log(val.magnitude()); + } else { + return std::log(val); + } + } + }; + } // namespace InterpolationTransforms + + template <typename Transform> + class CrossSectionTable { + public: + template <typename InputIt1, typename InputIt2> + CrossSectionTable(InputIt1 energiesFirst, InputIt1 energiesLast, + InputIt2 crosssectionsFirst) { + static_assert(std::is_same_v<typename InputIt1::value_type, HEPEnergyType>); + static_assert(std::is_same_v<typename InputIt2::value_type, CrossSectionType>); + + table_.reserve(std::distance(energiesFirst, energiesLast)); + + auto itE = boost::make_transform_iterator(std::move(energiesFirst), transform_); + decltype(itE) const itEEnd = + boost::make_transform_iterator(std::move(energiesLast), transform_); + + for (auto itXS = std::move(crosssectionsFirst); itE != itEEnd; ++itE, ++itXS) { + table_.emplace_back(*itE, *itXS); + } + + std::sort(table_.begin(), table_.end(), less_datapoint); + } + + CrossSectionType interpolate(HEPEnergyType energy) const { + auto const transformed_val = transform_(energy); + auto const lb_it = + std::lower_bound(table_.cbegin(), table_.cend(), transformed_val, less_x); + if (lb_it == table_.cbegin()) { + throw std::runtime_error{"CrossSectionTable: value out of bounds (lower limit)"}; + } + if (lb_it == table_.cend()) { + throw std::runtime_error{"CrossSectionTable: value out of bounds (upper limit)"}; + } + + auto const prev_it = std::prev(lb_it); + auto const lambda = + (transformed_val - prev_it->first) / (lb_it->first - prev_it->first); + return lambda * lb_it->second + (1 - lambda) * prev_it->second; + } + + private: + using key_type = std::invoke_result_t<Transform, HEPEnergyType>; + using datapoint_type = std::pair<key_type, CrossSectionType>; + std::vector<datapoint_type> table_; + Transform transform_{}; + + static bool less_datapoint(datapoint_type const& a, datapoint_type const& b) { + return a.first < b.first; + } + static bool less_x(datapoint_type const& a, typename datapoint_type::first_type b) { + return a.first < b; + } + }; +} // namespace corsika diff --git a/corsika/modules/Pythia8.hpp b/corsika/modules/Pythia8.hpp index fa5b65181..6973eca70 100644 --- a/corsika/modules/Pythia8.hpp +++ b/corsika/modules/Pythia8.hpp @@ -9,3 +9,18 @@ #include <corsika/modules/pythia8/Decay.hpp> #include <corsika/modules/pythia8/NeutrinoInteraction.hpp> +#include <corsika/modules/pythia8/InteractionModel.hpp> + +namespace corsika::pythia8 { + /** + * pythia8::Interaction is the process for ProcessSequence. + * + * The pythia8::InteractionModel is wrapped as an InteractionProcess here in order + * to provide all the functions for ProcessSequence. + */ + class Interaction : public InteractionModel, public InteractionProcess<Interaction> { + public: + Interaction(std::set<Code> const& stableList = {}) + : InteractionModel{stableList} {}; + }; +} // namespace corsika::pythia8 diff --git a/corsika/modules/pythia8/Interaction.hpp b/corsika/modules/pythia8/Interaction.hpp deleted file mode 100644 index 1187c550a..000000000 --- a/corsika/modules/pythia8/Interaction.hpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu - * - * This software is distributed under the terms of the 3-clause BSD license. - * See file LICENSE for a full version of the license. - */ - -#pragma once - -#include <tuple> -#include <boost/filesystem/path.hpp> - -#include <corsika/framework/utility/CorsikaData.hpp> -#include <corsika/framework/core/ParticleProperties.hpp> -#include <corsika/framework/core/PhysicalUnits.hpp> -#include <corsika/framework/random/RNGManager.hpp> -#include <corsika/framework/process/InteractionProcess.hpp> -#include <corsika/modules/pythia8/Pythia8.hpp> - -namespace corsika::pythia8 { - class Interaction : public InteractionProcess<Interaction> { - - public: - Interaction( - boost::filesystem::path const& mpiInitFile = corsika_data("Pythia/main184.mpi"), - bool const print_listing = false); - ~Interaction(); - - bool canInteract(Code const) const; - - bool isValid(Code const projectileId, Code const targetId, - HEPEnergyType const sqrtS) const; - /** - * Returns inelastic AND elastic cross sections. - * - * These cross sections must correspond to the process described in doInteraction - * AND elastic scattering (sigma_tot = sigma_inel + sigma_el). Allowed targets are: - * nuclei or single nucleons (p,n,hydrogen). This "InelEla" method is used since - * Sibyll must be useful inside the NuclearInteraction model, which requires that. - * - * @param projectile is the Code of the projectile - * @param target is the Code of the target - * @param sqrtSnn is the center-of-mass energy (per nucleon pair) - * @param Aprojectil is the mass number of the projectils, if it is a nucleus - * @param Atarget is the mass number of the target, if it is a nucleus - * - * @return a tuple of: inelastic cross section, elastic cross section - */ - std::tuple<CrossSectionType, CrossSectionType> getCrossSectionInelEla( - Code const projectile, Code const target, FourMomentum const& projectileP4, - FourMomentum const& targetP4) const; - - /** - * Returns inelastic (production) cross section. - * - * This cross section must correspond to the process described in doInteraction. - * Allowed targets are: nuclei or single nucleons (p,n,hydrogen). - * - * @param projectile is the Code of the projectile - * @param target is the Code of the target - * @param sqrtSnn is the center-of-mass energy (per nucleon pair) - * @param Aprojectil is the mass number of the projectils, if it is a nucleus - * @param Atarget is the mass number of the target, if it is a nucleus - * - * @return inelastic cross section - * elastic cross section - */ - CrossSectionType getCrossSection( - Code const projectile, Code const target, FourMomentum const& projectileP4, - FourMomentum const& targetP4) const; // non-const for now - - /** - * In this function PYTHIA is called to produce one event. The - * event is copied (and boosted) into the shower lab frame. - */ - template <typename TView> - void doInteraction(TView& output, Code const projectileId, Code const targetId, - FourMomentum const& projectileP4, FourMomentum const& targetP4); - - /** - * return average number of sub-collisions in a nucleus, using the parameterizations - * of Sjöstrand and Utheim, EPJ C 82 (2022) 1, 21, arXiv:2108.03481 [hep-ph] - * - * @param targetId target (nucleus) - * @param sigTot projectile-nucleon (=proton) cross-secion - */ - double getAverageSubcollisions(Code targetId, CrossSectionType sigTot) const; - - static std::array constexpr validTargets_{Code::Oxygen, Code::Nitrogen, - Code::Argon, Code::Hydrogen, - Code::Proton, Code::Neutron}; - - private: - default_prng_type& RNG_ = RNGManager<>::getInstance().getRandomStream("pythia"); - //~ bool const internalDecays_ = true; - int count_ = 0; - bool const print_listing_ = false; - Pythia8::Pythia pythiaMain_; - Pythia8::Pythia mutable pythiaColl_; // Pythia's getSigma...() are not marked const... - double const probSD_ = - 0.3; // Fraction of single diffractive events beyond first collision in nucleus. - HEPEnergyType const eMaxLab_ = 1e18_eV; - HEPEnergyType const eKinMinLab_ = 0.2_GeV; - }; - -} // namespace corsika::pythia8 - -#include <corsika/detail/modules/pythia8/Interaction.inl> diff --git a/corsika/modules/pythia8/InteractionModel.hpp b/corsika/modules/pythia8/InteractionModel.hpp new file mode 100644 index 000000000..290969ea1 --- /dev/null +++ b/corsika/modules/pythia8/InteractionModel.hpp @@ -0,0 +1,153 @@ +/* + * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu + * + * This software is distributed under the terms of the 3-clause BSD license. + * See file LICENSE for a full version of the license. + */ + +#pragma once + +#include <tuple> +#include <unordered_map> +#include <boost/filesystem/path.hpp> + +#include <corsika/framework/utility/CorsikaData.hpp> +#include <corsika/framework/core/ParticleProperties.hpp> +#include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/framework/random/RNGManager.hpp> +#include <corsika/framework/utility/CrossSectionTable.hpp> +#include <corsika/framework/process/InteractionProcess.hpp> +#include <corsika/modules/pythia8/Pythia8.hpp> + +namespace corsika::pythia8 { + + /** + * @brief Defines the interface to the PYTHIA8 interaction model. Configured for + * hadron-nucleus interactions using Angantyr. + * + * This is a TModel argument for InteractionProcess<TModel>. + */ + + class InteractionModel { + + public: + /** + * Constructs the interface for PYTHIA8 + * + * @param stableParticles - set of particle ids to be treated as stable inside + * pythia. if empty then all particles are treated as stable (no decays!) + * @param dataPath path to the pythia tables + * @param printListing switch on/off the pythia printout + */ + InteractionModel(std::set<Code> const& stableParticles = {}, + boost::filesystem::path const& dataPath = corsika_data("Pythia"), + bool const printListing = false); + ~InteractionModel(); + + bool canInteract(Code const) const; + + /** + * Check if configuration of projectile and target is valid. + * + * @param projectileId is the Code of the projectile + * @param targetId is the Code of the target + * @param sqrtS is the squared sum of the 4-momenta of projectile and target + */ + bool isValid(Code const projectileId, Code const targetId, + HEPEnergyType const sqrtS) const; + + /** + * Returns inelastic AND elastic cross sections. + * + * These cross sections must correspond to the process described in doInteraction + * (sigma_tot = sigma_inel + sigma_el). Allowed targets are: + * nuclei or single nucleons (p,n,hydrogen). + * + * @param projectile is the Code of the projectile + * @param target is the Code of the target + * @param projectileP4 is the 4-momentum of the projectile + * @param targetP4 is the 4-momentum of the target + * + * @return a tuple of: inelastic cross section, elastic cross section + */ + std::tuple<CrossSectionType, CrossSectionType> getCrossSectionInelEla( + Code const projectile, Code const target, FourMomentum const& projectileP4, + FourMomentum const& targetP4) const; + + /** + * Returns inelastic (production) cross section. + * + * This cross section must correspond to the process described in doInteraction. + * Allowed targets are: nuclei or single nucleons (p,n,hydrogen). + * + * @param projectile is the Code of the projectile + * @param target is the Code of the target + * @param projectileP4 is the 4-momentum of the projectile + * @param targetP4 is the 4-momentum of the target + * + * @return inelastic cross section + * elastic cross section + */ + CrossSectionType getCrossSection(Code const projectile, Code const target, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) const; + + /** + * In this function PYTHIA8 is called to produce one event. The + * event is copied (and boosted) into the reference frame defined by the input + * 4-momenta. + */ + template <typename TView> + void doInteraction(TView& output, Code const projectileId, Code const targetId, + FourMomentum const& projectileP4, FourMomentum const& targetP4); + + using key_type = std::pair<corsika::Code, corsika::Code>; + + private: + static auto constexpr GeV_mult = [](float x) { return x * 1_GeV; }; + static auto constexpr millibarn_mult = [](float x) { return x * 1_mb; }; + + static CrossSectionTable<InterpolationTransforms::Log> loadPPTable( + boost::filesystem::path const& dataPath, char const* key); + + default_prng_type& RNG_ = RNGManager<>::getInstance().getRandomStream("pythia"); + + /** + * All particles that we enable as projectiles. Code::Nucleus is not listed as it is + * handled separately. The list is unlikely to be exhaustive, but should cover the + * mainstream use-cases sufficiently well. + */ + + static std::array constexpr validProjectiles_ = { + Code::PiPlus, Code::PiMinus, Code::Pi0, Code::Proton, + Code::AntiProton, Code::Neutron, Code::AntiNeutron, Code::KPlus, + Code::KMinus, Code::K0Long, Code::K0Short, Code::SigmaMinus, + Code::SigmaPlusBar, Code::SigmaPlus, Code::SigmaMinusBar, Code::Xi0, + Code::Xi0Bar}; + + static std::array constexpr validTargets_ = { + Code::Proton, Code::Carbon, Code::Nitrogen, Code::Oxygen, Code::Argon}; + + std::unordered_map<corsika::Code, corsika::Code> const xs_map_ = { + {Code::SigmaMinus, Code::SigmaPlus}, + {Code::SigmaMinusBar, Code::SigmaPlus}, + {Code::SigmaPlusBar, Code::SigmaPlus}, + {Code::Xi0Bar, Code::Xi0}}; + + Pythia8::Pythia pythia_; + + std::unordered_map<key_type, CrossSectionTable<InterpolationTransforms::Log>> + crossSectionTables_; + + CrossSectionTable<InterpolationTransforms::Log> crossSectionPPElastic_, + crossSectionPPInelastic_; + + bool const print_listing_ = false; + HEPEnergyType const eMaxLab_ = + 1e21_eV; // Cross-section tables tabulated up to 10^21 eV + HEPEnergyType const eKinMinLab_ = 100_GeV; + }; + +} // namespace corsika::pythia8 + +#include <corsika/detail/modules/pythia8/InteractionModel.inl> diff --git a/corsika/modules/qgsjetII/InteractionModel.hpp b/corsika/modules/qgsjetII/InteractionModel.hpp index 543545388..7734831a0 100644 --- a/corsika/modules/qgsjetII/InteractionModel.hpp +++ b/corsika/modules/qgsjetII/InteractionModel.hpp @@ -54,6 +54,23 @@ namespace corsika::qgsjetII { FourMomentum const& projectileP4, FourMomentum const& targetP4) const; + /** + * Returns inelastic AND elastic cross sections. + * + * These cross sections must correspond to the process described in doInteraction + * AND elastic scattering (sigma_tot = sigma_inel + sigma_el). + * + * @param projectile is the Code of the projectile + * @param target is the Code of the target + * @param projectileP4: four-momentum of projectile + * @param targetP4: four-momentum of target + * + * @return a tuple of: inelastic cross section, elastic cross section + */ + std::tuple<CrossSectionType, CrossSectionType> getCrossSectionInelEla( + Code const projectile, Code const target, FourMomentum const& projectileP4, + FourMomentum const& targetP4) const; + /** * In this function QGSJETII is called to produce one event. * diff --git a/corsika/modules/sibyll/InteractionModel.hpp b/corsika/modules/sibyll/InteractionModel.hpp index 5deda8362..55fdd5208 100644 --- a/corsika/modules/sibyll/InteractionModel.hpp +++ b/corsika/modules/sibyll/InteractionModel.hpp @@ -29,6 +29,9 @@ namespace corsika::sibyll { CrossSectionType getCrossSection(Code, Code, FourMomentum const&, FourMomentum const&) const; + std::tuple<CrossSectionType, CrossSectionType> getCrossSectionInelEla( + Code, Code, FourMomentum const&, FourMomentum const&) const; + template <typename TSecondaries> void doInteraction(TSecondaries&, Code, Code, FourMomentum const&, FourMomentum const&); diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index ae67a0d5b..228c9b0de 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -81,6 +81,14 @@ CORSIKA_REGISTER_EXAMPLE (static_sequence) ## physics_examples ################### +add_executable (cross_section physics_examples/cross_section.cpp) +target_link_libraries (cross_section PRIVATE CORSIKA8::CORSIKA8) +CORSIKA_REGISTER_EXAMPLE (cross_section RUN_OPTIONS sibyll) + +add_executable (had_interactions physics_examples/had_interactions.cpp) +target_link_libraries (had_interactions PRIVATE CORSIKA8::CORSIKA8) +CORSIKA_REGISTER_EXAMPLE (had_interactions RUN_OPTIONS sibyll) + add_executable (stopping_power physics_examples/stopping_power.cpp) target_link_libraries (stopping_power PRIVATE CORSIKA8::CORSIKA8) CORSIKA_REGISTER_EXAMPLE (stopping_power) diff --git a/examples/physics_examples/cross_section.cpp b/examples/physics_examples/cross_section.cpp new file mode 100644 index 000000000..36645851e --- /dev/null +++ b/examples/physics_examples/cross_section.cpp @@ -0,0 +1,163 @@ +/* + * (c) Copyright 2019 CORSIKA Project, corsika-project@lists.kit.edu + * + * This software is distributed under the terms of the 3-clause BSD license. + * See file LICENSE for a full version of the license. + */ + +#include <corsika/modules/Epos.hpp> +#include <corsika/modules/Sibyll.hpp> +#include <corsika/modules/QGSJetII.hpp> +#include <corsika/modules/Pythia8.hpp> + +#include <corsika/framework/core/ParticleProperties.hpp> +#include <corsika/framework/core/PhysicalUnits.hpp> + +#include <corsika/framework/geometry/RootCoordinateSystem.hpp> +#include <corsika/framework/geometry/Vector.hpp> + +#include <corsika/framework/random/RNGManager.hpp> + +#include <corsika/setup/SetupC7trackedParticles.hpp> +#include <corsika/modules/Random.hpp> + +#include <fstream> +#include <string> +#include <tuple> + +using namespace corsika; + +/** + * Calculates the inelastic p-p cross section and the production cross section for + * p-Oxygen for a given interaction model using the C8 interface getCrossSection(ID1, ID2, + * P41, P42) where ID? and P4? are the particle Id and 4-momenta of projectile and target + * respectively. The input to getCrossSection can be ANY frame of reference. Internally + * the boost to the CM is calculated and the correct cross section is returned. + * calculate_cross_sections allows to switch between the CM and lab configurations. + * + * output is a table in ACSCII file cross-sections-<model-name>.txt + * + * Energy ranges from 100GeV to 10^12 GeV (10^11 eV to 10^21 eV) in the lab. frame + * table format is: "# i, E_lab (GeV), E_cm per nucleon (GeV), cross section p-p (mb), + * production " + * + * @param model the interaction model to use + * @param model_name string with model name + * + * @return a tuple of: inelastic cross section, elastic cross section + */ + +template <typename TModel> +void calculate_cross_sections(TModel& model, std::string const& model_name) { + + std::stringstream fname; + fname << "cross-sections-" << model_name << ".txt"; + std::ofstream out(fname.str()); + out << "# cross section table for " << model_name << "\n"; + out << "# i, P_lab (GeV), E_cm per nucleon (GeV), cross section p-p (mb), cross " + "section pi-p (mb), production " + "cross section p-O16 (mb), production cross section p-N14 (mb), production " + "cross section p-Ar40 (mb), cross section pi-O16 (mb), production cross " + "section, pi-N14 (mb), production, cross section pi-Ar40 (mb), cross section " + "He-O16 (mb), production cross section, He-N14 (mb), production, cross section " + "He-Ar40 (mb)\n"; + + CoordinateSystemPtr const& cs = get_root_CoordinateSystem(); + + float const amin = 2; + float const amax = 11; + int const nebins = 15; + float const da = (amax - amin) / (float)nebins; + + for (int i = 0; i < nebins; ++i) { + + corsika::units::si::HEPMomentumType const setP = pow(10, da * i + amin) * 1_GeV; + CrossSectionType xs_prod_hNuc[3][3] = {}; + CrossSectionType xs_prod_hp[3] = {}; + HEPEnergyType setE, comEnn; + int l = -1; + for (auto projId : {Code::Proton, Code::PiPlus, Code::Helium}) { + ++l; + setE = calculate_total_energy(setP, get_mass(projId)); + comEnn = corsika::calculate_com_energy(setE, get_mass(projId), + corsika::constants::nucleonMass); + + // four momenta in lab frame + corsika::FourMomentum p4Proj(setE, MomentumVector(cs, {0_eV, 0_eV, setP})); + corsika::FourMomentum p4protonTarg(Proton::mass, + MomentumVector(cs, {0_eV, 0_eV, 0_eV})); + + // had-p + auto const [xs_prod, xs_ela] = + model->getCrossSectionInelEla(projId, Code::Proton, p4Proj, p4protonTarg); + xs_prod_hp[l] = xs_prod; + + int k = -1; + for (auto targNuc : {Code::Oxygen, Code::Nitrogen, Code::Argon}) { + ++k; + FourMomentum const p4nucTarg = corsika::FourMomentum( + get_mass(targNuc), MomentumVector(cs, {0_eV, 0_eV, 0_eV})); + xs_prod_hNuc[l][k] = model->getCrossSection(projId, targNuc, p4Proj, p4nucTarg); + } + } + CORSIKA_LOG_INFO( + "Elab={:15.2f} GeV,Ecom={:15.2f} GeV, sig-p-p={:6.2f} mb, sig-pi-p={:6.2f} mb, " + "p-O={:6.2f} mb, p-N={:6.2f} mb, p-Ar={:6.2f} mb " + "pi-O={:6.2f} mb, pi-N={:6.2f} mb, pi-Ar={:6.2f} mb " + "He-O={:6.2f} mb, He-N={:6.2f} mb, He-Ar={:6.2f} mb", + setP / 1_GeV, comEnn / 1_GeV, xs_prod_hp[0] / 1_mb, xs_prod_hp[1] / 1_mb, + xs_prod_hNuc[0][0] / 1_mb, xs_prod_hNuc[0][1] / 1_mb, xs_prod_hNuc[0][2] / 1_mb, + xs_prod_hNuc[1][0] / 1_mb, xs_prod_hNuc[1][1] / 1_mb, xs_prod_hNuc[1][2] / 1_mb, + xs_prod_hNuc[2][0] / 1_mb, xs_prod_hNuc[2][1] / 1_mb, xs_prod_hNuc[2][2] / 1_mb); + + out << i << " " << setP / 1_GeV << " " << comEnn / 1_GeV << " " + << xs_prod_hp[0] / 1_mb << " " << xs_prod_hp[1] / 1_mb << " " + << xs_prod_hNuc[0][0] / 1_mb << " " << xs_prod_hNuc[0][1] / 1_mb << " " + << xs_prod_hNuc[0][2] / 1_mb << " " << xs_prod_hNuc[1][0] / 1_mb << " " + << xs_prod_hNuc[1][1] / 1_mb << " " << xs_prod_hNuc[1][2] / 1_mb << " " + << " " << xs_prod_hNuc[2][0] / 1_mb << " " << xs_prod_hNuc[2][1] / 1_mb << " " + << xs_prod_hNuc[2][2] / 1_mb << "\n"; + } + out.close(); + std::cout << "wrote cross section table for " << model_name + << " in file: " << fname.str() << std::endl; +} + +int main(int argc, char** argv) { + + logging::set_level(logging::level::info); + + if (argc != 2) { + std::cout << "usage: check <interaction model> \n valid models are: sibyll, " + "epos, qgsjet, pythia8" + << std::endl; + return 1; + } + std::string int_model_name = std::string(argv[1]); + + if (int_model_name == "sibyll") { + RNGManager<>::getInstance().registerRandomStream("sibyll"); + auto model = std::make_shared<corsika::sibyll::InteractionModel>( + std::set<Code>{Code::Proton, Code::Oxygen, Code::Nitrogen}, + corsika::setup::C7trackedParticles); + calculate_cross_sections(model, int_model_name); + } else if (int_model_name == "pythia8") { + RNGManager<>::getInstance().registerRandomStream("pythia"); + auto model = std::make_shared<corsika::pythia8::InteractionModel>(); + calculate_cross_sections(model, int_model_name); + + } else if (int_model_name == "epos") { + RNGManager<>::getInstance().registerRandomStream("epos"); + auto model = std::make_shared<corsika::epos::InteractionModel>( + corsika::setup::C7trackedParticles); + calculate_cross_sections(model, int_model_name); + } else if (int_model_name == "qgsjet") { + RNGManager<>::getInstance().registerRandomStream("qgsjet"); + auto model = std::make_shared<corsika::qgsjetII::InteractionModel>(); + calculate_cross_sections(model, int_model_name); + } else { + std::cout << "interaction model should be: sibyll, epos, qgsjet or pythia" + << std::endl; + return 1; + } +} diff --git a/examples/physics_examples/had_interactions.cpp b/examples/physics_examples/had_interactions.cpp new file mode 100644 index 000000000..5e61c91ad --- /dev/null +++ b/examples/physics_examples/had_interactions.cpp @@ -0,0 +1,267 @@ +/* + * (c) Copyright 2019 CORSIKA Project, corsika-project@lists.kit.edu + * + * This software is distributed under the terms of the 3-clause BSD license. + * See file LICENSE for a full version of the license. + */ + +#include <corsika/modules/Epos.hpp> +#include <corsika/modules/QGSJetII.hpp> +#include <corsika/modules/Sibyll.hpp> +#include <corsika/modules/Pythia8.hpp> + +#include <corsika/framework/core/ParticleProperties.hpp> +#include <corsika/framework/core/PhysicalUnits.hpp> + +#include <corsika/framework/geometry/Point.hpp> +#include <corsika/framework/geometry/RootCoordinateSystem.hpp> +#include <corsika/framework/geometry/Vector.hpp> + +#include <corsika/framework/random/RNGManager.hpp> + +#include <corsika/framework/core/EnergyMomentumOperations.hpp> + +#include <corsika/media/Environment.hpp> +#include <corsika/media/HomogeneousMedium.hpp> +#include <corsika/media/MediumPropertyModel.hpp> +#include <corsika/media/NuclearComposition.hpp> +#include <corsika/media/ShowerAxis.hpp> +#include <corsika/media/UniformMagneticField.hpp> + +#include <corsika/setup/SetupStack.hpp> +#include <corsika/setup/SetupC7trackedParticles.hpp> + +#include <corsika/modules/Random.hpp> + +#include <fstream> +#include <iostream> +#include <tuple> + +using namespace corsika; + +using EnvironmentInterface = IMediumPropertyModel<IMagneticFieldModel<IMediumModel>>; +using EnvType = Environment<EnvironmentInterface>; + +template <typename TModel> +void create_events(TModel& model, std::string const& model_name, Code const projectileId, + FourMomentum projectileP4, Code const targetId, FourMomentum targetP4, + int const nEvents) { + + logging::set_level(logging::level::info); + + // calculate Ecm per nucleon (sqrtSNN). Used only for output! + auto const projectileP4NN = + projectileP4 / (is_nucleus(projectileId) ? get_nucleus_A(projectileId) : 1); + auto const targetP4NN = targetP4 / (is_nucleus(targetId) ? get_nucleus_A(targetId) : 1); + auto const SNN = (projectileP4NN + targetP4NN).getNormSqr(); + HEPEnergyType const sqrtSNN = sqrt(SNN); + auto const sqrtS = (projectileP4 + targetP4).getNorm(); + CORSIKA_LOG_INFO("{} + {} interactions at sqrtSNN= {} GeV, sqrtS = {} GeV", + projectileId, targetId, sqrtSNN / 1_GeV, sqrtS / 1_GeV); + + // target material and environment (not used just there to create the C8 stack) + EnvType env; + auto& universe = *(env.getUniverse()); + CoordinateSystemPtr const& rootCS = env.getCoordinateSystem(); + auto world = EnvType::createNode<Sphere>(Point{rootCS, 0_m, 0_m, 0_m}, 150_km); + using MyHomogeneousModel = + MediumPropertyModel<UniformMagneticField<HomogeneousMedium<EnvironmentInterface>>>; + auto const props = world->setModelProperties<MyHomogeneousModel>( + Medium::AirDry1Atm, MagneticFieldVector(rootCS, 0_T, 0_T, 0_T), + 1_kg / (1_m * 1_m * 1_m), NuclearComposition({targetId}, {1.})); + world->setModelProperties(props); + universe.addChild(std::move(world)); + + // projectile (dummy) + auto const plab3 = projectileP4.getSpaceLikeComponents(); + auto p0 = Point(rootCS, 0_m, 0_m, 0_m); + setup::Stack<EnvType> stack; + stack.addParticle(std::make_tuple( + projectileId, calculate_kinetic_energy(plab3.getNorm(), get_mass(projectileId)), + plab3.normalized(), p0, 0_ns)); + auto particle = stack.first(); + particle.setNode(universe.getContainingNode(p0)); + + std::stringstream pname; + pname << "particles-" << model_name << "-" << projectileId << "-" << targetId << "-" + << sqrtSNN / 1_GeV << "GeV" + << ".txt"; + + unsigned int count_central_charged = 0; + HEPMomentumType total_pt = 0_GeV; + + std::ofstream pout(pname.str()); + // add header + pout << "# particle production in " << model_name << " for " << projectileId << " + " + << targetId << " interactions." + << "\n"; + pout << "# lab. momentum of projectile: " + << projectileP4.getSpaceLikeComponents().getNorm() / 1_GeV << " (GeV)" + << "\n"; + pout << "# CM energy per nucleon: " << sqrtSNN / 1_GeV << " (GeV)" + << "\n"; + pout << "# i, j, particle name, PDG id, p.rap, rap., energy (GeV), px, py, pz (GeV), " + "pt (GeV), charge" + << "\n"; + for (int i = 0; i < nEvents; ++i) { + + // empty stack from previous interaction + stack.clear(); + // add particle to get StackView to write secondaries to C8 stack + stack.addParticle(std::make_tuple( + projectileId, calculate_kinetic_energy(plab3.getNorm(), get_mass(projectileId)), + plab3.normalized(), p0, 0_ns)); // irrelevant + auto particle = stack.first(); + particle.setNode(universe.getContainingNode(p0)); + setup::Stack<EnvType>::stack_view_type output(particle); + + // generate secondaries and fill output + model->doInteraction(output, projectileId, targetId, projectileP4, targetP4); + + // loop through secondaries and calculate higher level variables (rapidity, pt, ...), + // write to file + int j = -1; + HEPMomentumType tpt = 0_GeV; + for (auto& secondary : output) { + ++j; + // pseudorapidity, rapidity + auto const p = secondary.getMomentum(); + auto const pz = p.getZ(rootCS); + auto const px = p.getX(rootCS); + auto const py = p.getY(rootCS); + auto const pabs = p.getNorm(); + auto const pt = sqrt(p.getSquaredNorm() - square(pz)); + auto const en = secondary.getEnergy(); + auto const mt = sqrt(square(pt) + square(get_mass(secondary.getPID()))); + double prap, rap; + if (pz / 1_GeV <= 0) { + rap = log(mt / (en - pz)); + prap = log((pt + 1.0_eV) / (pabs - pz)); + } else { + rap = log((en + pz) / mt); + prap = log((pabs + pz) / (pt + 1.0_eV)); + } + pout << i << " " << j << " " << secondary.getPID() << " " + << static_cast<int>(get_PDG(secondary.getPID())) << " " << prap << " " << rap + << " " << en / 1_GeV << " " << px / 1_GeV << " " << py / 1_GeV << " " + << pz / 1_GeV << " " << pt / 1_GeV << " " + << get_charge_number(secondary.getPID()) << "\n"; + // calculate plateau (CMS experiment) + if (abs(prap) < 2.5 && get_charge_number(secondary.getPID()) != 0) { + count_central_charged++; + } + tpt += pt; + } + total_pt += tpt / float(j); + } + std::cout << "average plateau height: " << count_central_charged / float(nEvents) + << std::endl; + std::cout << "average pt (GeV): " << total_pt / float(nEvents) / 1_GeV << std::endl; + std::cout << "wrote hadronic events for " << model_name << " in file: " << pname.str() + << std::endl; +}; + +int main(int argc, char** argv) { + + std::string int_model, projectile, target; + HEPMomentumType P0; + int Nevents = 10; + if (argc < 2 || argc > 7) { + std::cout << "usage: check <interaction model> \n valid models are: sibyll, " + "epos, qgsjet, pythia8. Other options are (in that order): <projectile> " + "<target> <projectile momentum (lab in GeV)> <no. of events>. Defaults " + "are: proton, proton, 100, 10" + << std::endl; + return 1; + } else if (argc == 2) { + int_model = std::string(argv[1]); + projectile = "proton"; + target = "proton"; + P0 = 100_GeV; + } else if (argc == 3) { + int_model = std::string(argv[1]); + projectile = std::string(argv[2]); + target = "proton"; + P0 = 100_GeV; + } else if (argc == 4) { + int_model = std::string(argv[1]); + projectile = std::string(argv[2]); + target = std::string(argv[3]); + P0 = 100_GeV; + } else if (argc == 5) { + int_model = std::string(argv[1]); + projectile = std::string(argv[2]); + target = std::string(argv[3]); + P0 = std::stoi(std::string(argv[4])) * 1_GeV; + } else if (argc == 6) { + int_model = std::string(argv[1]); + projectile = std::string(argv[2]); + target = std::string(argv[3]); + P0 = std::stoi(std::string(argv[4])) * 1_GeV; + Nevents = std::stoi(std::string(argv[5])); + } + + Code projectileId; + if (projectile == "proton") + projectileId = Code::Proton; + else if (projectile == "antiproton") + projectileId = Code::AntiProton; + else if (projectile == "pion") + projectileId = Code::PiPlus; + else if (projectile == "oxygen") + projectileId = Code::Oxygen; + else { + std::cout << "unknown projectile. pick proton, antiproton, pion or oxygen" + << std::endl; + return 0; + } + Code targetId; + if (target == "proton") + targetId = Code::Proton; + else if (target == "nitrogen") + targetId = Code::Nitrogen; + else { + std::cout << "unknown target. pick proton or nitrogen" << std::endl; + return 0; + } + + auto const rootCS = get_root_CoordinateSystem(); + FourMomentum const P4proj{ + calculate_total_energy(get_mass(projectileId), P0), + {rootCS, {HEPMomentumType::zero(), HEPMomentumType::zero(), P0}}}; + + FourMomentum const P4targ{ + calculate_total_energy(get_mass(targetId), HEPMomentumType::zero()), + {rootCS, + {HEPMomentumType::zero(), HEPMomentumType::zero(), HEPMomentumType::zero()}}}; + + /* + the interface to hadronic models in C8 has two layers. The class InteractionModel + (HadronInteractionModel in the case of sibyll) implements the interface to the + interaction model. It provides the basic interface functions getCrossSection and + doInteraction. The class Interaction extends the interface in InteractionModel to an + element C8 process sequence. Here we use only the interface class. + */ + if (int_model == "sibyll") { + RNGManager<>::getInstance().registerRandomStream("sibyll"); + // this is the hadron model in sibyll. can only do hadron-nucleus interactions + auto model = std::make_shared<corsika::sibyll::HadronInteractionModel>( + corsika::setup::C7trackedParticles); + create_events(model, int_model, projectileId, P4proj, targetId, P4targ, Nevents); + } else if (int_model == "epos") { + RNGManager<>::getInstance().registerRandomStream("epos"); + auto model = std::make_shared<corsika::epos::InteractionModel>( + corsika::setup::C7trackedParticles); + create_events(model, int_model, projectileId, P4proj, targetId, P4targ, Nevents); + } else if (int_model == "pythia8") { + RNGManager<>::getInstance().registerRandomStream("pythia"); + auto model = std::make_shared<corsika::pythia8::InteractionModel>( + corsika::setup::C7trackedParticles); + + create_events(model, int_model, projectileId, P4proj, targetId, P4targ, Nevents); + } else { + RNGManager<>::getInstance().registerRandomStream("qgsjet"); + auto model = std::make_shared<corsika::qgsjetII::InteractionModel>(); + create_events(model, int_model, projectileId, P4proj, targetId, P4targ, Nevents); + } +} diff --git a/modules/data b/modules/data index d8ac240ed..73f5a85a0 160000 --- a/modules/data +++ b/modules/data @@ -1 +1 @@ -Subproject commit d8ac240ed196fd9f9e697326b15b64ed164c47a3 +Subproject commit 73f5a85a068c84ca67437a8014511df0b43b8365 diff --git a/modules/pythia8/CMakeLists.txt b/modules/pythia8/CMakeLists.txt index d07add39c..5863b9891 100644 --- a/modules/pythia8/CMakeLists.txt +++ b/modules/pythia8/CMakeLists.txt @@ -51,14 +51,14 @@ if ("x_${USE_Pythia8_C8}" STREQUAL "x_SYSTEM") else () set (_C8_Pythia8_Download_Dir "https://pythia.org/download/pythia83") - set (_C8_Pythia8_VERSION "8312") + set (_C8_Pythia8_VERSION "8313") message (STATUS "Building modules/pythia8 using pythia${_C8_Pythia8_VERSION}.tar.bz2") message (STATUS "This will take a bit.....") # this is not a full Pythia8 install, it is a bit simplified, e.g. no pythia8-config, no examples # and also, it is fitted into the normal CORSIKA8 install "include/corsika_modules/Pythia8, lib/corsika" ExternalProject_Add (pythia8 URL ${_C8_Pythia8_Download_Dir}/pythia${_C8_Pythia8_VERSION}.tar.bz2 - URL_MD5 0acde09714b5383ac807edb10f161dd9 + URL_MD5 2bfcb4cefefca17605fab253016e6c6e SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/pythia8/source" INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/pythia8/install" CONFIGURE_COMMAND ./configure --prefix=${CMAKE_CURRENT_BINARY_DIR}/pythia8/install diff --git a/tests/modules/testPythia8.cpp b/tests/modules/testPythia8.cpp index dca606e5c..8b7edef7b 100644 --- a/tests/modules/testPythia8.cpp +++ b/tests/modules/testPythia8.cpp @@ -198,4 +198,6 @@ TEST_CASE("Pythia8Interface", "modules") { {Proton::mass, {rootCS, {0_eV, 0_eV, 0_eV}}}) == CrossSectionType::zero()); } -} + +#include <tests/modules/testPythia8Interaction.inl> +} \ No newline at end of file diff --git a/tests/modules/testPythia8Interaction.inl b/tests/modules/testPythia8Interaction.inl index 38d74c6dd..20860fadc 100644 --- a/tests/modules/testPythia8Interaction.inl +++ b/tests/modules/testPythia8Interaction.inl @@ -12,14 +12,23 @@ when this is merged the interaction tests can be activated again by including this file in testPytha8.cpp eg #include "tests/modules/testPythia8Interaction.inl" */ -SECTION("pythia interaction") { +#include "corsika/framework/core/EnergyMomentumOperations.hpp" +#include "corsika/framework/core/Logging.hpp" +#include "corsika/framework/core/PhysicalUnits.hpp" - // this will be a p-p collision at sqrts=3.5TeV -> no problem for pythia - auto [stackPtr, secViewPtr] = setup::testing::setup_stack( - Code::Proton, 7_TeV, (DummyEnvironment::BaseNodeType* const)nodePtr, *csPtr); - auto& view = *secViewPtr; +logging::set_level(logging::level::debug); - corsika::pythia8::Interaction collision; +// this will be a p-p collision at sqrts=3.5TeV -> no problem for pythia +auto [stackPtr, secViewPtr] = setup::testing::setup_stack( + Code::Proton, 7_TeV, (DummyEnvironment::BaseNodeType* const)nodePtr, *csPtr); +auto& view = *secViewPtr; + +std::set<Code> const tracked_hadrons = {Code::Proton, Code::Neutron, Code::Pi0, + Code::PiPlus}; + +corsika::pythia8::InteractionModel collision(tracked_hadrons); + +SECTION("pythia interaction configurations") { REQUIRE(collision.canInteract(Code::Proton)); REQUIRE(collision.canInteract(Code::AntiProton)); @@ -28,33 +37,42 @@ SECTION("pythia interaction") { REQUIRE(collision.canInteract(Code::PiMinus)); REQUIRE(collision.canInteract(Code::PiPlus)); REQUIRE_FALSE(collision.canInteract(Code::Electron)); + REQUIRE_FALSE(collision.canInteract(Code::MuPlus)); +} - // pi+p - REQUIRE(collision.getCrossSection( - Code::PiPlus, Code::Proton, - {sqrt(static_pow<2>(PiPlus::mass) + static_pow<2>(100_GeV)), - {rootCS, {0_eV, 0_eV, 100_GeV}}}, - {Proton::mass, {rootCS, {0_eV, 0_eV, 0_eV}}}) > 0_mb); +corsika::units::si::HEPMomentumType P0 = 10_TeV; - // pi+H - REQUIRE(collision.getCrossSection( - Code::PiPlus, Code::Hydrogen, - {sqrt(static_pow<2>(PiPlus::mass) + static_pow<2>(100_GeV)), - {rootCS, {0_eV, 0_eV, 100_GeV}}}, - {Hydrogen::mass, {rootCS, {0_eV, 0_eV, 0_eV}}}) > 0_mb); +SECTION("pythia interaction") { + // test some combinations of valid target and projectile particles + // so far only hadron-hadron and hadron-Nucleus is allowed + Code const target = GENERATE(Code::Nitrogen, Code::Oxygen, Code::Argon); + Code const projectile = GENERATE(Code::Proton, Code::PiPlus, Code::KPlus); + + CORSIKA_LOG_INFO("testing: {} - {}", projectile, target); + REQUIRE( + collision.getCrossSection( + projectile, target, + {calculate_total_energy(P0, get_mass(projectile)), {rootCS, {0_eV, 0_eV, P0}}}, + {get_mass(target), {rootCS, {0_eV, 0_eV, 0_eV}}}) > 0_mb); + + collision.doInteraction(view, projectile, target, + {corsika::calculate_total_energy(P0, get_mass(projectile)), + {rootCS, {0_eV, 0_eV, P0}}}, + {get_mass(target), {rootCS, {0_eV, 0_eV, 0_eV}}}); + REQUIRE(view.getSize() >= 2); +} - // K+{p,n,N,O,Ar} - Code const target = - GENERATE(Code::Proton, Code::Neutron, Code::Nitrogen, Code::Oxygen, Code::Argon); - REQUIRE(collision.getCrossSection( - Code::KPlus, target, - {sqrt(static_pow<2>(KPlus::mass) + static_pow<2>(100_GeV)), - {rootCS, {0_eV, 0_eV, 100_GeV}}}, - {get_mass(target), {rootCS, {0_eV, 0_eV, 0_eV}}}) > 0_mb); +SECTION("pythia interaction: previous angantyr fail, now fixed") { + // when initializing pythia with angantyr, kaon-proton or pion-proton interactions do + // not seem to work + Code const target = Code::Proton; + Code const projectile = GENERATE(Code::KPlus, Code::PiPlus); - collision.doInteraction(view, Code::Proton, target, - {sqrt(static_pow<2>(Proton::mass) + static_pow<2>(100_GeV)), - {rootCS, {0_eV, 0_eV, 100_GeV}}}, + CORSIKA_LOG_INFO("testing: {}-{}", projectile, target); + + collision.doInteraction(view, projectile, target, + {corsika::calculate_total_energy(P0, get_mass(projectile)), + {rootCS, {0_eV, 0_eV, P0}}}, {get_mass(target), {rootCS, {0_eV, 0_eV, 0_eV}}}); REQUIRE(view.getSize() >= 2); } @@ -123,26 +141,28 @@ SECTION("pythia wrong projectile") { Code::Iron, 1_GeV, (DummyEnvironment::BaseNodeType* const)nodePtr, *csPtr); { [[maybe_unused]] auto const& dummy_StackPtr = stackPtr; } + HEPMomentumType const P0 = 100_GeV; + corsika::pythia8::Interaction collision; REQUIRE(collision.getCrossSectionInelEla( Code::Electron, Code::Electron, - {sqrt(static_pow<2>(Electron::mass) + static_pow<2>(100_GeV)), - {rootCS, {0_eV, 0_eV, 100_GeV}}}, + {sqrt(static_pow<2>(Electron::mass) + static_pow<2>(P0)), + {rootCS, {0_eV, 0_eV, P0}}}, {Proton::mass, {rootCS, {0_eV, 0_eV, 0_eV}}}) == std::tuple{0_mb, 0_mb}); - REQUIRE_THROWS( - collision.doInteraction(*secViewPtr, Code::Helium, Code::Nitrogen, - {sqrt(static_pow<2>(Helium::mass) + static_pow<2>(100_GeV)), - {rootCS, {0_eV, 0_eV, 100_GeV}}}, - {Nitrogen::mass, {rootCS, {0_eV, 0_eV, 0_eV}}})); + REQUIRE_THROWS(collision.doInteraction( + *secViewPtr, Code::Helium, Code::Nitrogen, + {sqrt(static_pow<2>(Helium::mass) + static_pow<2>(P0)), {rootCS, {0_eV, 0_eV, P0}}}, + {Nitrogen::mass, {rootCS, {0_eV, 0_eV, 0_eV}}})); // gamma+p not possible REQUIRE(collision.getCrossSection( - Code::Photon, Code::Proton, {100_GeV, {rootCS, {0_eV, 0_eV, 100_GeV}}}, + Code::H0, Code::Proton, + {calculate_total_energy(P0, H0::mass), {rootCS, {0_eV, 0_eV, P0}}}, {Proton::mass, {rootCS, {0_eV, 0_eV, 0_eV}}}) == CrossSectionType::zero()); REQUIRE(collision.getCrossSectionInelEla( - Code::Photon, Code::Proton, {100_GeV, {rootCS, {0_eV, 0_eV, 100_GeV}}}, + Code::Photon, Code::Proton, {P0, {rootCS, {0_eV, 0_eV, P0}}}, {Proton::mass, {rootCS, {0_eV, 0_eV, 0_eV}}}) == std::make_tuple(CrossSectionType::zero(), CrossSectionType::zero())); } \ No newline at end of file -- GitLab