diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 144446321560e92191f4057aeedcaacf57b538c9..b632dda7765f723ffd79fc7dd1e14807fe4f57b5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -248,7 +248,7 @@ build_test_example-clang-8: - cmake --build . -- VERBOSE=1 -j2 - cmake --build . --target run_examples -- -j2 rules: - - if: '$CI_MERGE_REQUEST_LABELS =~ /Ready for code review/' # run on merge requests, if label 'Ready for code review' is set + - if: '$CI_MERGE_REQUEST_LABELS =~ /Ready for Code Review/' # run on merge requests, if label 'Ready for Code Review' is set - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH when: manual allow_failure: true @@ -307,7 +307,7 @@ coverage: - tar czf coverage-report.tar.gz coverage-report coverage: '/^.*lines\.+:\s(.*\%)\s/' rules: - - if: '$CI_MERGE_REQUEST_LABELS =~ /Ready for code review/' # run on merge requests, if label 'Ready for code review' is set + - if: '$CI_MERGE_REQUEST_LABELS =~ /Ready for Code Review/' # run on merge requests, if label 'Ready for Code Review' is set - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_MERGE_REQUEST_ID when: manual @@ -343,7 +343,7 @@ sanity: - cmake .. -DWITH_CORSIKA_SANITIZERS_ENABLED=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_Pythia8_C8=C8 - cmake --build . -- -j4 rules: - - if: '$CI_MERGE_REQUEST_LABELS =~ /Ready for code review/' # run on merge requests, if label 'Ready for code review' is set + - if: '$CI_MERGE_REQUEST_LABELS =~ /Ready for Code Review/' # run on merge requests, if label 'Ready for Code Review' is set - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH when: manual allow_failure: true diff --git a/corsika/detail/framework/core/Cascade.inl b/corsika/detail/framework/core/Cascade.inl index 64ca0a3aa5ff2f9fb0a95062d27ba4dcdba4807c..f11427148c96e927176168f6d9d8c1ee3dc9fc15 100644 --- a/corsika/detail/framework/core/Cascade.inl +++ b/corsika/detail/framework/core/Cascade.inl @@ -9,14 +9,21 @@ #pragma once #include <corsika/framework/core/PhysicalUnits.hpp> + #include <corsika/framework/process/ProcessReturn.hpp> #include <corsika/framework/process/ContinuousProcessStepLength.hpp> #include <corsika/framework/process/ContinuousProcessIndex.hpp> + #include <corsika/framework/random/ExponentialDistribution.hpp> #include <corsika/framework/random/RNGManager.hpp> #include <corsika/framework/random/UniformRealDistribution.hpp> + #include <corsika/framework/stack/SecondaryView.hpp> + +#include <corsika/framework/utility/COMBoost.hpp> + #include <corsika/media/Environment.hpp> +#include <corsika/media/NuclearComposition.hpp> #include <cassert> #include <cmath> @@ -40,7 +47,7 @@ namespace corsika { auto pNext = stack_.getNextParticle(); CORSIKA_LOG_TRACE( - "============== next particle : count={}, pid={}, " + "============== next particle : count={}, pid={}" ", stack entries={}" ", stack deleted={}", count_, pNext.getPID(), stack_.getEntries(), stack_.getErased()); @@ -59,32 +66,41 @@ namespace corsika { inline void Cascade<TTracking, TProcessList, TOutput, TStack>::forceInteraction() { CORSIKA_LOG_TRACE("forced interaction!"); setNodes(); - auto vParticle = stack_.getNextParticle(); - stack_view_type secondaries(vParticle); - interaction(secondaries, sequence_.getInverseInteractionLength(vParticle)); + auto particle = stack_.getNextParticle(); + stack_view_type secondaries(particle); + + auto const* currentLogicalNode = particle.getNode(); + // assert that particle stays outside void Universe if it has no + // model properties set + assert((currentLogicalNode != &*environment_.getUniverse() || + environment_.getUniverse()->hasModelProperties()) && + "FATAL: The environment model has no valid properties set!"); + NuclearComposition const& composition = + currentLogicalNode->getModelProperties().getNuclearComposition(); + + // determine projectile + HEPEnergyType const Elab = particle.getEnergy(); + FourMomentum const projectileP4{Elab, particle.getMomentum()}; + // determine cross section in material + CrossSectionType const sigma = + composition.getWeightedSum([=](Code const targetId) -> CrossSectionType { + FourMomentum const targetP4( + get_mass(targetId), + MomentumVector(particle.getMomentum().getCoordinateSystem(), + {0_GeV, 0_GeV, 0_GeV})); + return sequence_.getCrossSection(particle, targetId, targetP4); + }); + interaction(secondaries, projectileP4, composition, sigma); sequence_.doSecondaries(secondaries); - vParticle.erase(); // primary particle is done + particle.erase(); // primary particle is done } template <typename TTracking, typename TProcessList, typename TOutput, typename TStack> inline void Cascade<TTracking, TProcessList, TOutput, TStack>::step( - particle_type& vParticle) { - - // determine combined total interaction length (inverse) - InverseGrammageType const total_inv_lambda = - sequence_.getInverseInteractionLength(vParticle); - - // sample random exponential step length in grammage - ExponentialDistribution expDist(1 / total_inv_lambda); - GrammageType const next_interact = expDist(rng_); + particle_type& particle) { - CORSIKA_LOG_DEBUG( - "total_lambda={} g/cm2, " - ", next_interact={} g/cm2", - double((1. / total_inv_lambda) / 1_g * 1_cm * 1_cm), - double(next_interact / 1_g * 1_cm * 1_cm)); - - auto const* currentLogicalNode = vParticle.getNode(); + // determine the volume where the particle is (last) known to be + auto const* currentLogicalNode = particle.getNode(); // assert that particle stays outside void Universe if it has no // model properties set @@ -92,24 +108,52 @@ namespace corsika { environment_.getUniverse()->hasModelProperties()) && "FATAL: The environment model has no valid properties set!"); + NuclearComposition const& composition = + currentLogicalNode->getModelProperties().getNuclearComposition(); + + // determine projectile + HEPEnergyType const Elab = particle.getEnergy(); + FourMomentum const projectileP4{Elab, particle.getMomentum()}; + + // determine combined full inelastic cross section of the particles in the material + + CrossSectionType const total_cx = + composition.getWeightedSum([=](Code const targetId) -> CrossSectionType { + FourMomentum const targetP4( + get_mass(targetId), + MomentumVector(particle.getMomentum().getCoordinateSystem(), + {0_GeV, 0_GeV, 0_GeV})); + return sequence_.getCrossSection(particle, targetId, targetP4); + }); + + // calculate interaction length in medium + GrammageType const total_lambda = + (composition.getAverageMassNumber() * constants::u) / total_cx; + + // sample random exponential step length in grammage + ExponentialDistribution expDist(total_lambda); + GrammageType const next_interact = expDist(rng_); + + CORSIKA_LOG_DEBUG("total_lambda={} g/cm2, next_interact={} g/cm2", + double(total_lambda / 1_g * 1_cm * 1_cm), + double(next_interact / 1_g * 1_cm * 1_cm)); + // determine combined total inverse decay time - InverseTimeType const total_inv_lifetime = sequence_.getInverseLifetime(vParticle); + InverseTimeType const total_inv_lifetime = sequence_.getInverseLifetime(particle); // sample random exponential decay time ExponentialDistribution expDistDecay(1 / total_inv_lifetime); TimeType const next_decay = expDistDecay(rng_); - CORSIKA_LOG_DEBUG( - "total_lifetime={} s" - ", next_decay={} s", - (1 / total_inv_lifetime) / 1_s, next_decay / 1_s); + CORSIKA_LOG_DEBUG("total_lifetime={} ns, next_decay={} ns", + (1 / total_inv_lifetime) / 1_ns, next_decay / 1_ns); // convert next_decay from time to length [m] - LengthType const distance_decay = next_decay * vParticle.getMomentum().getNorm() / - vParticle.getEnergy() * constants::c; + LengthType const distance_decay = next_decay * particle.getMomentum().getNorm() / + particle.getEnergy() * constants::c; // determine geometric tracking - auto [step, nextVol] = tracking_.getTrack(vParticle); + auto [step, nextVol] = tracking_.getTrack(particle); auto geomMaxLength = step.getLength(1); // convert next_step from grammage to length @@ -119,7 +163,7 @@ namespace corsika { // determine the maximum geometric step length ContinuousProcessStepLength const continuousMaxStep = - sequence_.getMaxStepLength(vParticle, step); + sequence_.getMaxStepLength(particle, step); LengthType const continuous_max_dist = continuousMaxStep; // take minimum of geometry, interaction, decay for next step @@ -150,26 +194,25 @@ namespace corsika { // move particle along the trajectory to new position // also update momentum/direction/time step.setLength(min_distance); - vParticle.setPosition(step.getPosition(1)); - // assumption: tracking does not change absolute momentum (continuous physics can and - // will): - vParticle.setMomentum(step.getDirection(1) * vParticle.getMomentum().getNorm()); // apply all continuous processes on particle + track - if (sequence_.doContinuous(vParticle, step, limitingId) == + if (sequence_.doContinuous(particle, step, limitingId) == ProcessReturn::ParticleAbsorbed) { CORSIKA_LOG_DEBUG("Cascade: delete absorbed particle PID={} E={} GeV", - vParticle.getPID(), vParticle.getEnergy() / 1_GeV); - if (vParticle.isErased()) { + particle.getPID(), particle.getEnergy() / 1_GeV); + if (particle.isErased()) { CORSIKA_LOG_WARN( "Particle marked as Absorbed in doContinuous, but prematurely erased. This " "may be bug. Check."); } else { - vParticle.erase(); + particle.erase(); } - return; + return; // particle is gone -> return } - vParticle.setTime(vParticle.getTime() + step.getDuration()); + particle.setTime(particle.getTime() + step.getDuration()); + particle.setPosition(step.getPosition(1)); + particle.setMomentum(step.getDirection(1) * particle.getMomentum().getNorm()); + if (isContinuous) { return; // there is nothing further, step is finished } @@ -188,28 +231,29 @@ namespace corsika { if (nextVol == environment_.getUniverse().get()) { CORSIKA_LOG_DEBUG( "particle left physics world, is now in unknown space -> delete"); - vParticle.erase(); + particle.erase(); } - vParticle.setNode(nextVol); + particle.setNode(nextVol); /* doBoundary may delete the particle (or not) - caveat: any changes to vParticle, or even the production + caveat: any changes to particle, or even the production of new secondaries is currently not passed to ParticleCut, thus, particles outside the desired phase space may be produced. \todo: this must be fixed. */ - sequence_.doBoundaryCrossing(vParticle, *currentLogicalNode, *nextVol); + sequence_.doBoundaryCrossing(particle, *currentLogicalNode, *nextVol); return; // step finished } CORSIKA_LOG_DEBUG("step limit reached (e.g. deflection). nothing further happens."); + // final sanity check, no actions { auto const* numericalNodeAfterStep = - environment_.getUniverse()->getContainingNode(vParticle.getPosition()); + environment_.getUniverse()->getContainingNode(particle.getPosition()); CORSIKA_LOG_TRACE( "Geometry check: numericalNodeAfterStep={} currentLogicalNode={}", fmt::ptr(numericalNodeAfterStep), fmt::ptr(currentLogicalNode)); @@ -231,23 +275,22 @@ namespace corsika { // secondaries, b) the projectile particle deleted (or // changed) - stack_view_type secondaries(vParticle); + stack_view_type secondaries(particle); /* Create SecondaryView object on Stack. The data container remains untouched and identical, and 'projectile' is identical - to 'vParticle' above this line. However, - projectile.AddSecondaries populate the SecondaryView, which can + to 'particle' above this line. However, + projectile.addSecondaries populate the SecondaryView, which can then be used afterwards for further processing. Thus: it is - important to use projectile/view (and not vParticle) for Interaction, + important to use projectile/view (and not particle) for Interaction, and Decay! */ - - [[maybe_unused]] auto projectile = secondaries.getProjectile(); - if (distance_interact < distance_decay) { - interaction(secondaries, total_inv_lambda); + interaction(secondaries, projectileP4, composition, total_cx); } else { + [[maybe_unused]] auto projectile = secondaries.getProjectile(); + if (decay(secondaries, total_inv_lifetime) == ProcessReturn::Decayed) { if (secondaries.getSize() == 1 && projectile.getPID() == secondaries.getNextParticle().getPID()) { @@ -258,7 +301,7 @@ namespace corsika { } sequence_.doSecondaries(secondaries); - vParticle.erase(); + particle.erase(); } // namespace corsika template <typename TTracking, typename TProcessList, typename TOutput, typename TStack> @@ -266,19 +309,6 @@ namespace corsika { stack_view_type& view, InverseTimeType initial_inv_decay_time) { CORSIKA_LOG_DEBUG("decay"); -#ifdef DEBUG - InverseTimeType const actual_decay_time = sequence_.getInverseLifetime(view.parent()); - if (actual_decay_time * 0.99 > initial_inv_decay_time) { - CORSIKA_LOG_WARN( - "Decay time decreased during step! This leads to un-physical step length. " - "delta_inverse_decay_time={}", - (actual_decay_time != InverseTimeType::zero() && - initial_inv_decay_time != InverseTimeType::zero() - ? 1 / initial_inv_decay_time - 1 / actual_decay_time - : TimeType::zero())); - } -#endif - // one option is that decay_time is now larger (less // probability for decay) than it was before the step, thus, // no decay might actually occur and is allowed @@ -296,28 +326,20 @@ namespace corsika { template <typename TTracking, typename TProcessList, typename TOutput, typename TStack> inline ProcessReturn Cascade<TTracking, TProcessList, TOutput, TStack>::interaction( - stack_view_type& view, InverseGrammageType initial_inv_int_length) { - CORSIKA_LOG_DEBUG("collide"); + stack_view_type& view, FourMomentum const& projectileP4, + NuclearComposition const& composition, + CrossSectionType const initial_cross_section) { -#ifdef DEBUG - InverseGrammageType const actual_inv_length = sequence_.getInverseInteractionLength( - view.parent()); // 1/lambda_int after step, -dE/dX etc. - - if (actual_inv_length * 0.99 > initial_inv_int_length) { - CORSIKA_LOG_WARN( - "Interaction length decreased during step! This leads to un-physical step " - "length. delta_inverse_interaction_length={}", - 1 / initial_inv_int_length - 1 / actual_inv_length); - } -#endif + CORSIKA_LOG_DEBUG("collide"); - // one option is that interaction_length is now larger (less + // one option is that cross section is now smaller (less // probability for collision) than it was before the step, thus, // no interaction might actually occur and is allowed - UniformRealDistribution<InverseGrammageType> uniDist(initial_inv_int_length); - const auto sample_process = uniDist(rng_); - auto const returnCode = sequence_.selectInteraction(view, sample_process); + UniformRealDistribution<CrossSectionType> uniDist(initial_cross_section); + CrossSectionType const sample_process_by_cx = uniDist(rng_); + auto const returnCode = sequence_.selectInteraction(view, projectileP4, composition, + rng_, sample_process_by_cx); if (returnCode != ProcessReturn::Interacted) { CORSIKA_LOG_DEBUG("Particle did not interact!"); } diff --git a/corsika/detail/framework/core/ParticleProperties.inl b/corsika/detail/framework/core/ParticleProperties.inl index feb10651867f431b71afae8814353a11116cc4d8..0671a0db6453204feef2448055cc520b8f7c30a1 100644 --- a/corsika/detail/framework/core/ParticleProperties.inl +++ b/corsika/detail/framework/core/ParticleProperties.inl @@ -115,6 +115,11 @@ namespace corsika { inline HEPMassType constexpr get_nucleus_mass(Code const code) { unsigned int const A = get_nucleus_A(code); unsigned int const Z = get_nucleus_Z(code); + return get_nucleus_mass(A, Z); + } + + inline HEPMassType constexpr get_nucleus_mass(unsigned int const A, + unsigned int const Z) { return get_mass(Code::Proton) * Z + (A - Z) * get_mass(Code::Neutron); } diff --git a/corsika/detail/framework/process/InteractionCounter.inl b/corsika/detail/framework/process/InteractionCounter.inl index 7a549d61da111e3a338b4b19d2b01cf7e5cc7ce2..33ff338618a030cf9c1f4825f0961a2b7e29865b 100644 --- a/corsika/detail/framework/process/InteractionCounter.inl +++ b/corsika/detail/framework/process/InteractionCounter.inl @@ -18,22 +18,20 @@ namespace corsika { template <class TCountedProcess> template <typename TSecondaryView> - inline void InteractionCounter<TCountedProcess>::doInteraction(TSecondaryView& view) { - auto const projectile = view.getProjectile(); - auto const massNumber = projectile.getNode() - ->getModelProperties() - .getNuclearComposition() - .getAverageMassNumber(); + inline void InteractionCounter<TCountedProcess>::doInteraction( + TSecondaryView& view, Code const projectileId, Code const targetId, + FourMomentum const& projectileP4, FourMomentum const& targetP4) { + size_t const massNumber = is_nucleus(targetId) ? get_nucleus_A(targetId) : 1; auto const massTarget = massNumber * constants::nucleonMass; - histogram_.fill(projectile.getPID(), projectile.getEnergy(), massTarget); - process_.doInteraction(view); + histogram_.fill(projectileId, projectileP4.getTimeLikeComponent(), massTarget); + process_.doInteraction(view, projectileId, targetId, projectileP4, targetP4); } template <class TCountedProcess> - template <typename TParticle> - inline GrammageType InteractionCounter<TCountedProcess>::getInteractionLength( - TParticle const& particle) const { - return process_.getInteractionLength(particle); + inline CrossSectionType InteractionCounter<TCountedProcess>::getCrossSection( + Code const projectileId, Code const targetId, FourMomentum const& projectileP4, + FourMomentum const& targetP4) const { + return process_.getCrossSection(projectileId, targetId, projectileP4, targetP4); } template <class TCountedProcess> diff --git a/corsika/detail/framework/process/InteractionProcess.hpp b/corsika/detail/framework/process/InteractionProcess.hpp index 2446a385d7beec2424da598585dd89bd70466a5c..7319027194015b5caaf23946a1c12d2b4fd79c84 100644 --- a/corsika/detail/framework/process/InteractionProcess.hpp +++ b/corsika/detail/framework/process/InteractionProcess.hpp @@ -14,10 +14,12 @@ namespace corsika { /** - traits test for InteractionProcess::doInteraction method - */ + * @file InteractionProcess.hpp + * + * traits test for InteractionProcess::doInteraction methods etc. + */ - template <class TProcess, typename TReturn, typename... TArgs> + template <class TProcess, typename TReturn, typename TTemplate, typename... TArgs> struct has_method_doInteract : public detail::has_method_signature<TReturn, TArgs...> { ///! method signature @@ -29,7 +31,7 @@ namespace corsika { //! signature of templated method template <class T> - static decltype(testSignature(&T::template doInteraction<TArgs...>)) test( + static decltype(testSignature(&T::template doInteraction<TTemplate>)) test( std::nullptr_t); //! signature of non-templated method @@ -38,26 +40,25 @@ namespace corsika { public: /** - @name traits results - @{ - */ + * @name traits results + * @{ + */ using type = decltype(test<std::decay_t<TProcess>>(nullptr)); static const bool value = type::value; //! @} }; - //! @file InteractionProcess.hpp //! value traits type - template <class TProcess, typename TReturn, typename... TArgs> + template <class TProcess, typename TReturn, typename TTemplate, typename... TArgs> bool constexpr has_method_doInteract_v = - has_method_doInteract<TProcess, TReturn, TArgs...>::value; + has_method_doInteract<TProcess, TReturn, TTemplate, TArgs...>::value; /** - traits test for InteractionProcess::getInteractionLength method - */ + * traits test for TEMPLATED InteractionProcess::getCrossSection method (PROPOSAL). + */ - template <class TProcess, typename TReturn, typename... TArgs> - struct has_method_getInteractionLength + template <class TProcess, typename TReturn, typename TTemplate, typename... TArgs> + struct has_method_getCrossSectionTemplate : public detail::has_method_signature<TReturn, TArgs...> { ///! method signature @@ -69,28 +70,65 @@ namespace corsika { //! templated parameter option template <class T> - static decltype(testSignature(&T::template getInteractionLength<TArgs...>)) test( + static decltype(testSignature(&T::template getCrossSection<TTemplate>)) test( std::nullptr_t); //! non templated parameter option template <class T> - static decltype(testSignature(&T::getInteractionLength)) test(std::nullptr_t); + static decltype(testSignature(&T::getCrossSection)) test(std::nullptr_t); public: /** - @name traits results - @{ - */ + * @name traits results + * @{ + */ using type = decltype(test<std::decay_t<TProcess>>(nullptr)); static const bool value = type::value; //! @} }; - //! @file InteractionProcess.hpp - //! value traits type + //! value traits type shortcut + template <class TProcess, typename TReturn, typename TTemplate, typename... TArgs> + bool constexpr has_method_getCrossSectionTemplate_v = + has_method_getCrossSectionTemplate<TProcess, TReturn, TTemplate, TArgs...>::value; + + /** + * traits test for InteractionProcess::getCrossSection method. + */ + + template <class TProcess, typename TReturn, typename... TArgs> + struct has_method_getCrossSection + : public detail::has_method_signature<TReturn, TArgs...> { + + ///! method signature + using detail::has_method_signature<TReturn, TArgs...>::testSignature; + + //! the default value + template <class T> + static std::false_type test(...); + + //! templated parameter option + template <class T> + static decltype(testSignature(&T::template getCrossSection<TArgs...>)) test( + std::nullptr_t); + + //! non templated parameter option + template <class T> + static decltype(testSignature(&T::getCrossSection)) test(std::nullptr_t); + + public: + /** + * @name traits results + * @{ + */ + using type = decltype(test<std::decay_t<TProcess>>(nullptr)); + static const bool value = type::value; + //! @} + }; + //! value traits type shortcut template <class TProcess, typename TReturn, typename... TArgs> - bool constexpr has_method_getInteractionLength_v = - has_method_getInteractionLength<TProcess, TReturn, TArgs...>::value; + bool constexpr has_method_getCrossSection_v = + has_method_getCrossSection<TProcess, TReturn, TArgs...>::value; } // namespace corsika diff --git a/corsika/detail/framework/process/ProcessSequence.inl b/corsika/detail/framework/process/ProcessSequence.inl index 4d381abb16af98729d6defdfb53bfc0503d420f9..25a592d52642c3b9586b867659578f46c9dc5657 100644 --- a/corsika/detail/framework/process/ProcessSequence.inl +++ b/corsika/detail/framework/process/ProcessSequence.inl @@ -9,6 +9,7 @@ #pragma once #include <corsika/framework/core/PhysicalUnits.hpp> + #include <corsika/framework/process/BaseProcess.hpp> #include <corsika/framework/process/BoundaryCrossingProcess.hpp> #include <corsika/framework/process/ContinuousProcess.hpp> @@ -259,7 +260,8 @@ namespace corsika { template <typename TParticle, typename TTrack> inline ContinuousProcessStepLength ProcessSequence<TProcess1, TProcess2, IndexStart, IndexProcess1, - IndexProcess2>::getMaxStepLength(TParticle& particle, TTrack& vTrack) { + IndexProcess2>::getMaxStepLength(TParticle&& particle, + TTrack&& vTrack) { // if no other process in the sequence implements it ContinuousProcessStepLength max_length(std::numeric_limits<double>::infinity() * meter); @@ -309,24 +311,58 @@ namespace corsika { template <typename TProcess1, typename TProcess2, int IndexStart, int IndexProcess1, int IndexProcess2> template <typename TParticle> - inline InverseGrammageType - ProcessSequence<TProcess1, TProcess2, IndexStart, IndexProcess1, - IndexProcess2>::getInverseInteractionLength(TParticle&& particle) { + inline CrossSectionType + ProcessSequence<TProcess1, TProcess2, IndexStart, IndexProcess1, IndexProcess2>:: + getCrossSection([[maybe_unused]] TParticle const& projectile, + [[maybe_unused]] Code const targetId, + [[maybe_unused]] FourMomentum const& targetP4) const { - InverseGrammageType tot = 0 * meter * meter / gram; // default value + CrossSectionType tot = CrossSectionType::zero(); if constexpr (is_process_v<process1_type>) { // to protect from further compiler // errors if process1_type is invalid - if constexpr (is_interaction_process_v<process1_type> || - process1_type::is_process_sequence) { - tot += A_.getInverseInteractionLength(particle); + if constexpr (is_interaction_process_v<process1_type>) { + + bool constexpr has_signature_cx1 = + has_method_getCrossSection_v<TProcess1, // process object + CrossSectionType, // return type + Code, Code, // parameters + FourMomentum const&, FourMomentum const&>; + + if constexpr (has_signature_cx1) { + tot += A_.getCrossSection(projectile.getPID(), targetId, + {projectile.getEnergy(), projectile.getMomentum()}, + targetP4); + } else { // for PROPOSAL + tot += A_.getCrossSection(projectile, projectile.getPID(), + {projectile.getEnergy(), projectile.getMomentum()}); + } + + } else if constexpr (process1_type::is_process_sequence) { + tot += A_.getCrossSection(projectile, targetId, targetP4); } } if constexpr (is_process_v<process2_type>) { // to protect from further compiler // errors if process2_type is invalid - if constexpr (is_interaction_process_v<process2_type> || - process2_type::is_process_sequence) { - tot += B_.getInverseInteractionLength(particle); + if constexpr (is_interaction_process_v<process2_type>) { + + bool constexpr has_signature_cx1 = + has_method_getCrossSection_v<TProcess2, // process object + CrossSectionType, // return type + Code, Code, // parameters + FourMomentum const&, FourMomentum const&>; + + if constexpr (has_signature_cx1) { + tot += B_.getCrossSection(projectile.getPID(), targetId, + {projectile.getEnergy(), projectile.getMomentum()}, + targetP4); + } else { // for PROPOSAL + tot += B_.getCrossSection(projectile, projectile.getPID(), + {projectile.getEnergy(), projectile.getMomentum()}); + } + + } else if constexpr (process2_type::is_process_sequence) { + tot += B_.getCrossSection(projectile, targetId, targetP4); } } return tot; @@ -407,65 +443,190 @@ namespace corsika { template <typename TProcess1, typename TProcess2, int IndexStart, int IndexProcess1, int IndexProcess2> - template <typename TSecondaryView> + template <typename TSecondaryView, typename TRNG> inline ProcessReturn ProcessSequence<TProcess1, TProcess2, IndexStart, IndexProcess1, IndexProcess2>:: - selectInteraction(TSecondaryView& view, - [[maybe_unused]] InverseGrammageType lambda_inv_select, - [[maybe_unused]] InverseGrammageType lambda_inv_sum) { + selectInteraction(TSecondaryView&& view, FourMomentum const& projectileP4, + [[maybe_unused]] NuclearComposition const& composition, + [[maybe_unused]] TRNG&& rng, + [[maybe_unused]] CrossSectionType const cx_select, + [[maybe_unused]] CrossSectionType cx_sum) { - // TODO: add check for lambda_inv_select > lambda_inv_tot + // TODO: add check for cx_select > cx_tot if constexpr (is_process_v<process1_type>) { // to protect from further compiler // errors if process1_type is invalid if constexpr (process1_type::is_process_sequence) { // if A is a process sequence --> check inside ProcessReturn const ret = - A_.selectInteraction(view, lambda_inv_select, lambda_inv_sum); + A_.selectInteraction(view, projectileP4, composition, rng, cx_select, cx_sum); // if A_ did succeed, stop routine. Not checking other static branch B_. if (ret != ProcessReturn::Ok) { return ret; } } else if constexpr (is_interaction_process_v<process1_type>) { - // if this is not a ContinuousProcess --> evaluate probability - lambda_inv_sum += A_.getInverseInteractionLength(view.parent()); - // check if we should execute THIS process and then EXIT - if (lambda_inv_select <= lambda_inv_sum) { - // interface checking on TProcess1 - static_assert(has_method_doInteract_v<TProcess1, void, TSecondaryView&>, - "TDerived has no method with correct signature \"void " - "doInteraction(TSecondaryView&)\" required for " - "InteractionProcess<TDerived>. "); + auto const& projectile = view.parent(); + Code const projectileId = projectile.getPID(); + + // get cross section vector for all material components + // for selected process A + + bool constexpr has_signature_cx1 = + has_method_getCrossSection_v<TProcess1, // process object + CrossSectionType, // return type + Code, Code, // parameters + FourMomentum const&, FourMomentum const&>; + bool constexpr has_signature_cx2 = // needed for PROPOSAL interface + has_method_getCrossSectionTemplate_v< + TProcess1, // process object + CrossSectionType, // return type + decltype(projectile) const&, // template argument + decltype(projectile) const&, // parameters + Code, FourMomentum const&>; + + static_assert((has_signature_cx1 || has_signature_cx2), + "TProcess1 has no method with correct signature \"CrossSectionType " + "getCrossSection(Code, Code, FourMomentum const&, FourMomentum " + "const&)\" required by " + "InteractionProcess<TProcess1>. "); + + std::vector<CrossSectionType> weightedCrossSections; + if constexpr (has_signature_cx1) { + /*std::vector<CrossSectionType> const*/ weightedCrossSections = + composition.getWeighted([=](Code const targetId) -> CrossSectionType { + FourMomentum const targetP4( + get_mass(targetId), + MomentumVector(projectile.getMomentum().getCoordinateSystem(), + {0_GeV, 0_GeV, 0_GeV})); + return A_.getCrossSection(projectileId, targetId, projectileP4, targetP4); + }); + + cx_sum += + std::accumulate(weightedCrossSections.cbegin(), + weightedCrossSections.cend(), CrossSectionType::zero()); + + } else { // this is for PROPOSAL + cx_sum += A_.template getCrossSection(projectile, projectileId, projectileP4); + } + + // check if we should execute THIS process and then EXIT + if (cx_select <= cx_sum) { + + if constexpr (has_signature_cx1) { + // now also sample targetId from weighted cross sections + Code const targetId = composition.sampleTarget(weightedCrossSections, rng); + FourMomentum const targetP4( + get_mass(targetId), + MomentumVector(projectile.getMomentum().getCoordinateSystem(), + {0_GeV, 0_GeV, 0_GeV})); + + // interface checking on TProcess1 + static_assert( + has_method_doInteract_v<TProcess1, // process object + void, // return type + TSecondaryView, // template argument + TSecondaryView&, // method parameters + Code, Code, FourMomentum const&, + FourMomentum const&>, + "TProcess1 has no method with correct signature \"void " + "doInteraction<TSecondaryView>(TSecondaryView&, " + "Code, Code, FourMomentum const&, FourMomentum const&)\" required for " + "InteractionProcess<TProcess1>. "); + + A_.template doInteraction(view, projectileId, targetId, projectileP4, + targetP4); + + } else { // this is for PROPOSAL + A_.template doInteraction(view, projectileId, projectileP4); + } - A_.template doInteraction(view); return ProcessReturn::Interacted; } - } // end branch A - } + } + } // end branch A if constexpr (is_process_v<process2_type>) { // to protect from further compiler // errors if process2_type is invalid if constexpr (process2_type::is_process_sequence) { // if B_ is a process sequence --> check inside - return B_.selectInteraction(view, lambda_inv_select, lambda_inv_sum); + return B_.selectInteraction(view, projectileP4, composition, rng, cx_select, + cx_sum); } else if constexpr (is_interaction_process_v<process2_type>) { - // if this is not a ContinuousProcess --> evaluate probability - lambda_inv_sum += B_.getInverseInteractionLength(view.parent()); - // soon as SecondaryView::parent() is migrated! - // check if we should execute THIS process and then EXIT - if (lambda_inv_select <= lambda_inv_sum) { - // interface checking on TProcess1 - static_assert(has_method_doInteract_v<TProcess2, void, TSecondaryView&>, - "TDerived has no method with correct signature \"void " - "doInteraction(TSecondaryView&)\" required for " - "InteractionProcess<TDerived>. "); + auto const& projectile = view.parent(); + Code const projectileId = projectile.getPID(); + + // get cross section vector for all material components, for selected process B + bool constexpr has_signature_cx1 = + has_method_getCrossSection_v<TProcess2, // process object + CrossSectionType, // return type + Code, Code, // parameters + FourMomentum const&, FourMomentum const&>; + bool constexpr has_signature_cx2 = // needed for PROPOSAL interface + has_method_getCrossSectionTemplate_v< + TProcess2, // process object + CrossSectionType, // return type + decltype(*projectile) const&, // template argument + decltype(*projectile) const&, // parameters + Code, // parameters + FourMomentum const&>; + static_assert((has_signature_cx1 || has_signature_cx2), + "TProcess2 has no method with correct signature \"CrossSectionType " + "getCrossSection(Code, Code, FourMomentum const&, FourMomentum " + "const&)\" required by " + "InteractionProcess<TProcess1>. "); + + std::vector<CrossSectionType> weightedCrossSections; + if constexpr (has_signature_cx1) { + /* std::vector<CrossSectionType> const*/ weightedCrossSections = + composition.getWeighted([=](Code const targetId) -> CrossSectionType { + FourMomentum const targetP4( + get_mass(targetId), + MomentumVector(projectile.getMomentum().getCoordinateSystem(), + {0_GeV, 0_GeV, 0_GeV})); + return B_.getCrossSection(projectileId, targetId, projectileP4, targetP4); + }); + + cx_sum += + std::accumulate(weightedCrossSections.begin(), weightedCrossSections.end(), + CrossSectionType::zero()); + } else { // this is for PROPOSAL + cx_sum += B_.template getCrossSection(projectile, projectileId, projectileP4); + } - B_.doInteraction(view); + // check if we should execute THIS process and then EXIT + if (cx_select <= cx_sum) { + + if constexpr (has_signature_cx1) { + + // now also sample targetId from weighted cross sections + Code const targetId = composition.sampleTarget(weightedCrossSections, rng); + FourMomentum const targetP4( + get_mass(targetId), + MomentumVector(projectile.getMomentum().getCoordinateSystem(), + {0_GeV, 0_GeV, 0_GeV})); + + // interface checking on TProcess2 + static_assert( + has_method_doInteract_v<TProcess2, // process object + void, // return type + TSecondaryView, // template argument + TSecondaryView&, // method parameters + Code, Code, FourMomentum const&, + FourMomentum const&>, + "TProcess1 has no method with correct signature \"void " + "doInteraction<TSecondaryView>(TSecondaryView&, " + "Code, Code, FourMomentum const&, FourMomentum const&)\" required for " + "InteractionProcess<TProcess2>. "); + + B_.doInteraction(view, projectileId, targetId, projectileP4, targetP4); + } else { // this is for PROPOSAL + B_.doInteraction(view, projectileId, projectileP4); + } return ProcessReturn::Interacted; } - } // end branch B_ - } + } + } // end branch B_ return ProcessReturn::Ok; } @@ -501,7 +662,7 @@ namespace corsika { template <typename TSecondaryView> inline ProcessReturn ProcessSequence< TProcess1, TProcess2, IndexStart, IndexProcess1, - IndexProcess2>::selectDecay(TSecondaryView& view, + IndexProcess2>::selectDecay(TSecondaryView&& view, [[maybe_unused]] InverseTimeType decay_inv_select, [[maybe_unused]] InverseTimeType decay_inv_sum) { diff --git a/corsika/detail/framework/process/SwitchProcessSequence.inl b/corsika/detail/framework/process/SwitchProcessSequence.inl index 634395f55e0a59524263df2f9d72ebe80cac8be6..0d26014fda26eb3d7fbe57f4801b0ccd2f014824 100644 --- a/corsika/detail/framework/process/SwitchProcessSequence.inl +++ b/corsika/detail/framework/process/SwitchProcessSequence.inl @@ -210,82 +210,233 @@ namespace corsika { template <typename TCondition, typename TSequence, typename USequence, int IndexStart, int IndexProcess1, int IndexProcess2> template <typename TParticle> - inline InverseGrammageType SwitchProcessSequence< + CrossSectionType SwitchProcessSequence< TCondition, TSequence, USequence, IndexStart, IndexProcess1, - IndexProcess2>::getInverseInteractionLength(TParticle&& particle) { - - if (select_(particle)) { - if constexpr (is_interaction_process_v<process1_type> || - process1_type::is_process_sequence) { - return A_.getInverseInteractionLength(particle); + IndexProcess2>::getCrossSection(TParticle const& projectile, Code const targetId, + FourMomentum const& targetP4) const { + + if (select_(projectile)) { + if constexpr (is_interaction_process_v<process1_type>) { + bool constexpr has_signature_cx1 = + has_method_getCrossSection_v<TSequence, // process object + CrossSectionType, // return type + Code, Code, // parameters + FourMomentum const&, FourMomentum const&>; + if constexpr (has_signature_cx1) { + + return A_.getCrossSection(projectile.getPID(), targetId, + {projectile.getEnergy(), projectile.getMomentum()}, + targetP4); + } else { + return A_.getCrossSection(projectile, projectile.getPID(), + {projectile.getEnergy(), projectile.getMomentum()}); + } + } else if (process1_type::is_process_sequence) { + return A_.getCrossSection(projectile, targetId, targetP4); } } else { - - if constexpr (is_interaction_process_v<process2_type> || - process2_type::is_process_sequence) { - return B_.getInverseInteractionLength(particle); + if constexpr (is_interaction_process_v<process2_type>) { + bool constexpr has_signature_cx1 = + has_method_getCrossSection_v<USequence, // process object + CrossSectionType, // return type + Code, Code, // parameters + FourMomentum const&, FourMomentum const&>; + if constexpr (has_signature_cx1) { + + return B_.getCrossSection(projectile.getPID(), targetId, + {projectile.getEnergy(), projectile.getMomentum()}, + targetP4); + } else { + return B_.getCrossSection(projectile, targetId, targetP4); + } + } else if (process2_type::is_process_sequence) { + return B_.getCrossSection(projectile, targetId, targetP4); } } - return 0 * meter * meter / gram; // default value + return CrossSectionType::zero(); // default value } template <typename TCondition, typename TSequence, typename USequence, int IndexStart, int IndexProcess1, int IndexProcess2> - template <typename TSecondaryView> - inline ProcessReturn SwitchProcessSequence<TCondition, TSequence, USequence, IndexStart, - IndexProcess1, IndexProcess2>:: - selectInteraction(TSecondaryView& view, - [[maybe_unused]] InverseGrammageType lambda_inv_select, - [[maybe_unused]] InverseGrammageType lambda_inv_sum) { + template <typename TSecondaryView, typename TRNG> + inline ProcessReturn SwitchProcessSequence< + TCondition, TSequence, USequence, IndexStart, IndexProcess1, + IndexProcess2>::selectInteraction(TSecondaryView& view, + FourMomentum const& projectileP4, + NuclearComposition const& composition, TRNG& rng, + [[maybe_unused]] CrossSectionType const cx_select, + [[maybe_unused]] CrossSectionType cx_sum) { + if (select_(view.parent())) { if constexpr (process1_type::is_process_sequence) { // if A_ is a process sequence --> check inside - ProcessReturn const ret = - A_.selectInteraction(view, lambda_inv_select, lambda_inv_sum); - // if A_ did succeed, stop routine. Not checking other static branch B_. - if (ret != ProcessReturn::Ok) { return ret; } + return A_.selectInteraction(view, projectileP4, composition, rng, cx_select, + cx_sum); } else if constexpr (is_interaction_process_v<process1_type>) { - // if this is not a ContinuousProcess --> evaluate probability - lambda_inv_sum += A_.getInverseInteractionLength(view.parent()); - // check if we should execute THIS process and then EXIT - if (lambda_inv_select < lambda_inv_sum) { - // interface checking on TSequence - static_assert(has_method_doInteract_v<TSequence, void, TSecondaryView&>, - "TDerived has no method with correct signature \"void " - "doInteraction(TSecondaryView&)\" required for " - "InteractionProcess<TDerived>. "); + auto const& projectile = view.parent(); + Code const projectileId = projectile.getPID(); + + // get cross section vector for all material components + // for selected process A + bool constexpr has_signature_cx1 = + has_method_getCrossSection_v<TSequence, // process object + CrossSectionType, // return type + Code, Code, // parameters + FourMomentum const&, FourMomentum const&>; + bool constexpr has_signature_cx2 = // needed for PROPOSAL interface + has_method_getCrossSectionTemplate_v< + TSequence, // process object + CrossSectionType, // return type + decltype(projectile) const&, // template argument + decltype(projectile) const&, // parameters + Code, FourMomentum const&>; + + static_assert((has_signature_cx1 || has_signature_cx2), + "TSequence has no method with correct signature \"CrossSectionType " + "getCrossSection(Code, Code, FourMomentum const&, FourMomentum " + "const&)\" required by " + "InteractionProcess<TSequence>. "); + + std::vector<CrossSectionType> weightedCrossSections; + if constexpr (has_signature_cx1) { + /*std::vector<CrossSectionType> const*/ weightedCrossSections = + composition.getWeighted([=](Code const targetId) -> CrossSectionType { + FourMomentum const targetP4( + get_mass(targetId), + MomentumVector(projectile.getMomentum().getCoordinateSystem(), + {0_GeV, 0_GeV, 0_GeV})); + return A_.getCrossSection(projectileId, targetId, projectileP4, targetP4); + }); + + cx_sum += + std::accumulate(weightedCrossSections.cbegin(), + weightedCrossSections.cend(), CrossSectionType::zero()); + } else { // this is for PROPOSAL + cx_sum += A_.template getCrossSection(projectile, projectileId, projectileP4); + } + + if (cx_select < cx_sum) { + + if constexpr (has_signature_cx1) { + + // now also sample targetId from weighted cross sections + Code const targetId = composition.sampleTarget(weightedCrossSections, rng); + FourMomentum const targetP4( + get_mass(targetId), + MomentumVector(projectile.getMomentum().getCoordinateSystem(), + {0_GeV, 0_GeV, 0_GeV})); + + // interface checking on TProcess1 + static_assert( + has_method_doInteract_v<TSequence, // process object + void, // return type + TSecondaryView, // template argument + TSecondaryView&, // method parameters + Code, Code, FourMomentum const&, + FourMomentum const&>, + "TSequence has no method with correct signature \"void " + "doInteraction<TSecondaryView>(TSecondaryView&, " + "Code, Code, FourMomentum const&, FourMomentum const&)\" required for " + "InteractionProcess<TSequence>. "); + + A_.template doInteraction(view, projectileId, targetId, projectileP4, + targetP4); + } else { // this is for PROPOSAL + A_.template doInteraction(view, projectileId, projectileP4); + } - A_.doInteraction(view); return ProcessReturn::Interacted; - } - } // end branch A_ + } // end collision branch A + } - } else { + } else { // selection: end branch A, start branch B if constexpr (process2_type::is_process_sequence) { // if B_ is a process sequence --> check inside - return B_.selectInteraction(view, lambda_inv_select, lambda_inv_sum); + return B_.selectInteraction(view, projectileP4, composition, rng, cx_select, + cx_sum); } else if constexpr (is_interaction_process_v<process2_type>) { - // if this is not a ContinuousProcess --> evaluate probability - lambda_inv_sum += B_.getInverseInteractionLength(view.parent()); - // check if we should execute THIS process and then EXIT - if (lambda_inv_select < lambda_inv_sum) { - // interface checking on TSequence - static_assert(has_method_doInteract_v<USequence, void, TSecondaryView&>, - "TDerived has no method with correct signature \"void " - "doInteraction(TSecondaryView&)\" required for " - "InteractionProcess<TDerived>. "); + auto const& projectile = view.parent(); + Code const projectileId = projectile.getPID(); + + // get cross section vector for all material components, for selected process B + bool constexpr has_signature_cx1 = + has_method_getCrossSection_v<USequence, // process object + CrossSectionType, // return type + Code, Code, // parameters + FourMomentum const&, FourMomentum const&>; + bool constexpr has_signature_cx2 = // needed for PROPOSAL interface + has_method_getCrossSectionTemplate_v< + USequence, // process object + CrossSectionType, // return type + decltype(projectile) const&, // template argument + decltype(projectile) const&, // parameters + Code, FourMomentum const&>; + + static_assert((has_signature_cx1 || has_signature_cx2), + "USequence has no method with correct signature \"CrossSectionType " + "getCrossSection(Code, Code, FourMomentum const&, FourMomentum " + "const&)\" required by " + "InteractionProcess<USequence>. "); + + std::vector<CrossSectionType> weightedCrossSections; + if constexpr (has_signature_cx1) { + /* std::vector<CrossSectionType> const*/ weightedCrossSections = + composition.getWeighted([=](Code const targetId) -> CrossSectionType { + FourMomentum const targetP4( + get_mass(targetId), + MomentumVector(projectile.getMomentum().getCoordinateSystem(), + {0_GeV, 0_GeV, 0_GeV})); + return B_.getCrossSection(projectileId, targetId, projectileP4, targetP4); + }); + + cx_sum += + std::accumulate(weightedCrossSections.begin(), weightedCrossSections.end(), + CrossSectionType::zero()); + } else { // this is for PROPOSAL + cx_sum += B_.template getCrossSection(projectile, projectileId, projectileP4); + } + + // check if we should execute THIS process and then EXIT + if (cx_select <= cx_sum) { + + if constexpr (has_signature_cx1) { + + // now also sample targetId from weighted cross sections + Code const targetId = composition.sampleTarget(weightedCrossSections, rng); + FourMomentum const targetP4( + get_mass(targetId), + MomentumVector(projectile.getMomentum().getCoordinateSystem(), + {0_GeV, 0_GeV, 0_GeV})); + + // interface checking on TProcess2 + static_assert( + has_method_doInteract_v<USequence, // process object + void, // return type + TSecondaryView, // template argument + TSecondaryView&, // method parameters + Code, Code, FourMomentum const&, + FourMomentum const&>, + "USequence has no method with correct signature \"void " + "doInteraction<TSecondaryView>(TSecondaryView&, " + "Code, Code, FourMomentum const&, FourMomentum const&)\" required for " + "InteractionProcess<USequence>. "); + + B_.doInteraction(view, projectileId, targetId, projectileP4, targetP4); + } else { // this is for PROPOSAL + B_.doInteraction(view, projectileId, projectileP4); + } - B_.doInteraction(view); return ProcessReturn::Interacted; - } - } // end branch B_ - } + } // end collision in branch B + } + } // end branch B_ + return ProcessReturn::Ok; - } + } // namespace corsika template <typename TCondition, typename TSequence, typename USequence, int IndexStart, int IndexProcess1, int IndexProcess2> diff --git a/corsika/detail/framework/utility/COMBoost.inl b/corsika/detail/framework/utility/COMBoost.inl index 9b05bef0e5e5d0193d74b637416a0056b520c535..4905b940ba2ff53e2055ed68fc9ad6c5b4da6969 100644 --- a/corsika/detail/framework/utility/COMBoost.inl +++ b/corsika/detail/framework/utility/COMBoost.inl @@ -19,17 +19,15 @@ namespace corsika { - inline COMBoost::COMBoost(FourVector<HEPEnergyType, MomentumVector> const& Pprojectile, + inline COMBoost::COMBoost(FourMomentum const& P4projectile, HEPMassType const massTarget) - : originalCS_{Pprojectile.getSpaceLikeComponents().getCoordinateSystem()} - , rotatedCS_{ - make_rotationToZ(Pprojectile.getSpaceLikeComponents().getCoordinateSystem(), - Pprojectile.getSpaceLikeComponents())} { - auto const pProjectile = Pprojectile.getSpaceLikeComponents(); + : originalCS_{P4projectile.getSpaceLikeComponents().getCoordinateSystem()} + , rotatedCS_{make_rotationToZ(originalCS_, P4projectile.getSpaceLikeComponents())} { + auto const pProjectile = P4projectile.getSpaceLikeComponents(); auto const pProjNormSquared = pProjectile.getSquaredNorm(); auto const pProjNorm = sqrt(pProjNormSquared); - auto const eProjectile = Pprojectile.getTimeLikeComponent(); + auto const eProjectile = P4projectile.getTimeLikeComponent(); auto const massProjectileSquared = eProjectile * eProjectile - pProjNormSquared; auto const s = massTarget * massTarget + massProjectileSquared + 2 * eProjectile * massTarget; @@ -39,12 +37,38 @@ namespace corsika { auto const coshEta = sqrt(1 + pProjNormSquared / s); setBoost(coshEta, sinhEta); + CORSIKA_LOG_TRACE("COMBoost (1-beta)={}, gamma={}, det={}", 1 - sinhEta / coshEta, + coshEta, boost_.determinant() - 1); + } + inline COMBoost::COMBoost(FourMomentum const& P4projectile, + FourMomentum const& P4target) + : originalCS_{P4projectile.getSpaceLikeComponents().getCoordinateSystem()} { + + // this is the center-of-momentum CM frame + auto const pCM = + P4projectile.getSpaceLikeComponents() + P4target.getSpaceLikeComponents(); + auto const pCM2 = pCM.getSquaredNorm(); + auto const pCMnorm = sqrt(pCM2); + if (pCMnorm == 0_eV) { + // CM is at reset + rotatedCS_ = originalCS_; + } else { + rotatedCS_ = make_rotationToZ(originalCS_, P4projectile.getSpaceLikeComponents() + + P4target.getSpaceLikeComponents()); + } + + auto const s = (P4projectile + P4target).getNormSqr(); + auto const sqrtS = sqrt(s); + auto const sinhEta = -pCMnorm / sqrtS; + auto const coshEta = sqrt(1 + pCM2 / s); + + setBoost(coshEta, sinhEta); CORSIKA_LOG_TRACE("COMBoost (1-beta)={}, gamma={}, det={}", 1 - sinhEta / coshEta, coshEta, boost_.determinant() - 1); } - inline COMBoost::COMBoost(MomentumVector const& momentum, HEPEnergyType mass) + inline COMBoost::COMBoost(MomentumVector const& momentum, HEPEnergyType const mass) : originalCS_{momentum.getCoordinateSystem()} , rotatedCS_{make_rotationToZ(momentum.getCoordinateSystem(), momentum)} { auto const squaredNorm = momentum.getSquaredNorm(); @@ -52,15 +76,17 @@ namespace corsika { auto const sinhEta = -norm / mass; auto const coshEta = sqrt(1 + squaredNorm / (mass * mass)); setBoost(coshEta, sinhEta); + CORSIKA_LOG_TRACE("COMBoost (1-beta)={}, gamma={}, det={}", 1 - sinhEta / coshEta, + coshEta, boost_.determinant() - 1); } template <typename FourVector> - inline FourVector COMBoost::toCoM(FourVector const& p) const { - auto pComponents = p.getSpaceLikeComponents().getComponents(rotatedCS_); + inline FourVector COMBoost::toCoM(FourVector const& p4) const { + auto pComponents = p4.getSpaceLikeComponents().getComponents(rotatedCS_); Eigen::Vector3d eVecRotated = pComponents.getEigenVector(); Eigen::Vector2d lab; - lab << (p.getTimeLikeComponent() * (1 / 1_GeV)), + lab << (p4.getTimeLikeComponent() * (1 / 1_GeV)), (eVecRotated(2) * (1 / 1_GeV).magnitude()); auto const boostedZ = boost_ * lab; @@ -68,21 +94,23 @@ namespace corsika { eVecRotated(2) = boostedZ(1) * (1_GeV).magnitude(); + CORSIKA_LOG_TRACE("E0={}, p={}, E0'={}, p'={}", p4.getTimeLikeComponent() / 1_GeV, + eVecRotated(2) * (1 / 1_GeV).magnitude(), E_CoM / 1_GeV, boostedZ); + return FourVector(E_CoM, MomentumVector(rotatedCS_, eVecRotated)); } template <typename FourVector> - inline FourVector COMBoost::fromCoM(FourVector const& p) const { - auto pCM = p.getSpaceLikeComponents().getComponents(rotatedCS_); - auto const Ecm = p.getTimeLikeComponent(); + inline FourVector COMBoost::fromCoM(FourVector const& p4) const { + auto pCM = p4.getSpaceLikeComponents().getComponents(rotatedCS_); + auto const Ecm = p4.getTimeLikeComponent(); Eigen::Vector2d com; com << (Ecm * (1 / 1_GeV)), (pCM.getEigenVector()(2) * (1 / 1_GeV).magnitude()); - CORSIKA_LOG_TRACE( - "COMBoost::fromCoM Ecm={} GeV" - " pcm={} GeV (norm = {} GeV), invariant mass={} GeV", - Ecm / 1_GeV, pCM / 1_GeV, pCM.getNorm() / 1_GeV, p.getNorm() / 1_GeV); + CORSIKA_LOG_TRACE("Ecm={} GeV, pcm={} GeV (norm = {} GeV), invariant mass={} GeV", + Ecm / 1_GeV, pCM / 1_GeV, pCM.getNorm() / 1_GeV, + p4.getNorm() / 1_GeV); auto const boostedZ = inverseBoost_ * com; auto const E_lab = boostedZ(0) * 1_GeV; @@ -92,22 +120,22 @@ namespace corsika { Vector<typename decltype(pCM)::dimension_type> pLab{rotatedCS_, pCM}; pLab.rebase(originalCS_); - FourVector f(E_lab, pLab); - CORSIKA_LOG_TRACE("COMBoost::fromCoM --> Elab={} GeV", " plab={} GeV (norm={} GeV) " " GeV), invariant mass = {}", - E_lab / 1_GeV, f.getNorm() / 1_GeV, pLab.getComponents(), - pLab.getNorm() / 1_GeV); + E_lab / 1_GeV, FourVector{E_lab, pLab}.getNorm() / 1_GeV, + pLab.getComponents(), pLab.getNorm() / 1_GeV); - return f; + return FourVector{E_lab, pLab}; } - inline void COMBoost::setBoost(double coshEta, double sinhEta) { + inline void COMBoost::setBoost(double const coshEta, double const sinhEta) { boost_ << coshEta, sinhEta, sinhEta, coshEta; inverseBoost_ << coshEta, -sinhEta, -sinhEta, coshEta; } inline CoordinateSystemPtr COMBoost::getRotatedCS() const { return rotatedCS_; } + inline CoordinateSystemPtr COMBoost::getOriginalCS() const { return originalCS_; } + } // namespace corsika diff --git a/corsika/detail/media/NuclearComposition.inl b/corsika/detail/media/NuclearComposition.inl index 4995799d58ba35e30c09448946d67c50f8e29ade..c499a593d98f9ba064b0656ead63956fc3a78244 100644 --- a/corsika/detail/media/NuclearComposition.inl +++ b/corsika/detail/media/NuclearComposition.inl @@ -23,31 +23,52 @@ namespace corsika { inline NuclearComposition::NuclearComposition(std::vector<Code> const& pComponents, - std::vector<float> const& pFractions) + std::vector<double> const& pFractions) : numberFractions_(pFractions) , components_(pComponents) - , avgMassNumber_(std::inner_product( - pComponents.cbegin(), pComponents.cend(), pFractions.cbegin(), 0., - std::plus<double>(), [](auto const compID, auto const fraction) -> double { - if (is_nucleus(compID)) { - return get_nucleus_A(compID) * fraction; - } else { - return get_mass(compID) / convert_SI_to_HEP(constants::u) * fraction; - } - })) { - assert(pComponents.size() == pFractions.size()); - auto const sumFractions = - std::accumulate(pFractions.cbegin(), pFractions.cend(), 0.f); - - if (!(0.999f < sumFractions && sumFractions < 1.001f)) { + , avgMassNumber_(getWeightedSum([](Code const compID) -> double { + if (is_nucleus(compID)) { + return get_nucleus_A(compID); + } else { + return get_mass(compID) / convert_SI_to_HEP(constants::u); + } + })) { + if (pComponents.size() != pFractions.size()) { + throw std::runtime_error( + "Cannot construct NuclearComposition from vectors of different sizes."); + } + auto const sumFractions = std::accumulate(pFractions.cbegin(), pFractions.cend(), 0.); + + if (!(0.999 < sumFractions && sumFractions < 1.001)) { throw std::runtime_error("element fractions do not add up to 1"); } this->updateHash(); } template <typename TFunction> - inline auto NuclearComposition::getWeightedSum(TFunction const& func) const { - using ResultQuantity = decltype(func(*components_.cbegin())); + inline auto NuclearComposition::getWeighted(TFunction const& func) const { + using ResultQuantity = decltype(func(std::declval<Code>())); + auto const product = [&](auto const compID, auto const fraction) { + return func(compID) * fraction; + }; + + if constexpr (phys::units::is_quantity_v<ResultQuantity>) { + std::vector<ResultQuantity> result(components_.size(), ResultQuantity::zero()); + std::transform(components_.cbegin(), components_.cend(), numberFractions_.cbegin(), + result.begin(), product); + return result; + } else { + std::vector<ResultQuantity> result(components_.size(), ResultQuantity(0)); + std::transform(components_.cbegin(), components_.cend(), numberFractions_.cbegin(), + result.begin(), product); + return result; + } + } // namespace corsika + + template <typename TFunction> + inline auto NuclearComposition::getWeightedSum(TFunction const& func) const + -> decltype(func(std::declval<Code>())) { + using ResultQuantity = decltype(func(std::declval<Code>())); auto const prod = [&](auto const compID, auto const fraction) { return func(compID) * fraction; @@ -68,7 +89,7 @@ namespace corsika { inline size_t NuclearComposition::getSize() const { return numberFractions_.size(); } - inline std::vector<float> const& NuclearComposition::getFractions() const { + inline std::vector<double> const& NuclearComposition::getFractions() const { return numberFractions_; } @@ -82,16 +103,14 @@ namespace corsika { template <class TRNG> inline Code NuclearComposition::sampleTarget(std::vector<CrossSectionType> const& sigma, - TRNG& randomStream) const { - - assert(sigma.size() == numberFractions_.size()); + TRNG&& randomStream) const { + if (sigma.size() != numberFractions_.size()) { + throw std::runtime_error("incompatible vector sigma as input"); + } std::discrete_distribution channelDist( - WeightProviderIterator<decltype(numberFractions_.begin()), - decltype(sigma.begin())>(numberFractions_.begin(), - sigma.begin()), - WeightProviderIterator<decltype(numberFractions_.begin()), decltype(sigma.end())>( - numberFractions_.end(), sigma.end())); + WeightProviderIterator(numberFractions_.begin(), sigma.begin()), + WeightProviderIterator(numberFractions_.end(), sigma.end())); auto const iChannel = channelDist(randomStream); return components_[iChannel]; @@ -99,6 +118,7 @@ namespace corsika { // Note: when this class ever modifies its internal data, the hash // must be updated, too! + // the hash value is important to find tables, etc. inline size_t NuclearComposition::getHash() const { return hash_; } inline bool NuclearComposition::operator==(NuclearComposition const& v) const { @@ -107,7 +127,8 @@ namespace corsika { inline void NuclearComposition::updateHash() { std::vector<std::size_t> hashes; - for (float ifrac : this->getFractions()) hashes.push_back(std::hash<float>{}(ifrac)); + for (double ifrac : this->getFractions()) + hashes.push_back(std::hash<double>{}(ifrac)); for (Code icode : this->getComponents()) hashes.push_back(std::hash<int>{}(static_cast<int>(icode))); std::size_t h = std::hash<double>{}(this->getAverageMassNumber()); diff --git a/corsika/detail/modules/epos/Interaction.inl b/corsika/detail/modules/epos/Interaction.inl deleted file mode 100644 index 1e69cff4eb6ec68037b9ae4fcb943cb4f45e5665..0000000000000000000000000000000000000000 --- a/corsika/detail/modules/epos/Interaction.inl +++ /dev/null @@ -1,584 +0,0 @@ -/* - * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu - * - * This software is distributed under the terms of the GNU General Public - * Licence version 3 (GPL Version 3). See file LICENSE for a full version of - * the license. - */ - -#pragma once - -#include <corsika/modules/epos/Interaction.hpp> -#include <corsika/modules/epos/EposStack.hpp> - -#include <corsika/media/Environment.hpp> -#include <corsika/media/NuclearComposition.hpp> - -#include <corsika/framework/utility/COMBoost.hpp> -#include <corsika/framework/utility/CorsikaData.hpp> - -#include <corsika/setup/SetupStack.hpp> -#include <corsika/setup/SetupTrajectory.hpp> - -#include <epos.hpp> - -#include <string> -#include <tuple> - -using namespace corsika; -using SetupParticle = setup::Stack::stack_iterator_type; - -namespace corsika::epos { - - inline Interaction::Interaction(std::string const& dataPath, - bool const epos_printout_on) - : data_path_(dataPath) - , epos_listing_(epos_printout_on) { - if (dataPath == "") { - data_path_ = (std::string(corsika_data("EPOS").c_str()) + "/").c_str(); - } - // initialize Eposlhc - static bool initialized = false; - if (!initialized) { - initialize(); - initialized = true; - } - setParticlesStable(); - } - - inline void Interaction::setParticlesStable() const { - CORSIKA_LOGGER_DEBUG(logger_, - "set all particles known to CORSIKA stable inside EPOS.."); - for (auto& p : get_all_particles()) { - if (!is_hadron(p)) continue; - int const eid = convertToEposRaw(p); - if (eid != 0) { - ::epos::nodcy_.nrnody = ::epos::nodcy_.nrnody + 1; - ::epos::nodcy_.nody[::epos::nodcy_.nrnody - 1] = eid; - } - } - } - - inline bool Interaction::isValidTarget(Code const TargetId) const { - return is_nucleus(TargetId) && (get_nucleus_A(TargetId) < maxTargetMassNumber_); - } - - inline void Interaction::initialize() const { - - CORSIKA_LOGGER_DEBUG(logger_, "initializing..."); - - // corsika7 ini - int iarg = 0; - ::epos::aaset_(iarg); - - // debug output settings - ::epos::prnt1_.ish = 0; // debug level in epos, 0: off, 6: medium output - ::epos::prnt3_.iwseed = 0; // 1: printout seeds, 0: off - ::epos::files_.ifch = 6; // output unit, 6: screen - - // dummy set seeds for random number generator in epos. need to fool epos checks... - // we will use external generator - ::epos::cseed_.seedi = 1; - ::epos::cseed_.seedj = 1; - ::epos::cseed_.seedc = 1; - - ::epos::enrgy_.egymin = minEnergyCoM_ / 1_GeV; // 6.; - ::epos::enrgy_.egymax = maxEnergyCoM_ / 1_GeV; // 2.e6; - - ::epos::lhcparameters_(); - - ::epos::hadr6_.isigma = 0; // do not show cross section - ::epos::hadr6_.isetcs = 3; /* !option to obtain pomeron parameters - ! 0.....determine parameters but do not use Kfit - ! 1.....determine parameters and use Kfit - ! else..get from table - ! should be sufficiently detailed - ! say iclegy1=1,iclegy2=99 - ! table is always done, more or less detailed!!! - !and option to use cross section tables - ! 2....tabulation - ! 3....simulation - */ - ::epos::cjinti_.ionudi = - 1; // !include quasi elastic events but strict calculation of xs - ::epos::cjinti_.iorsce = 0; // !color exchange turned on(1) or off(0) - ::epos::cjinti_.iorsdf = 3; // !droplet formation turned on(>0) or off(0) - ::epos::cjinti_.iorshh = 0; // !other hadron-hadron int. turned on(1) or off(0) - - ::epos::othe1_.istore = 0; // do not produce epos output file - ::epos::nucl6_.infragm = - 2; // 0: keep free nucleons in fragmentation,1: one fragment, 2: fragmentation - - ::epos::othe2_.iframe = 12; // lab frame, target at rest - - // set paths to tables in corsika data - ::epos::datadir BASE(data_path_); - strcpy(::epos::fname_.fnnx, BASE.data); - ::epos::nfname_.nfnnx = BASE.length; - - ::epos::datadir TL(data_path_ + "epos.initl"); - strcpy(::epos::fname_.fnii, TL.data); - ::epos::nfname_.nfnii = TL.length; - - ::epos::datadir EV(data_path_ + "epos.iniev"); - strcpy(::epos::fname_.fnie, EV.data); - ::epos::nfname_.nfnie = EV.length; - - ::epos::datadir RJ(data_path_ + "epos.inirj"); // lhcparameters adds ".lhc" - strcpy(::epos::fname_.fnrj, RJ.data); - ::epos::nfname_.nfnrj = RJ.length; - - ::epos::datadir CS(data_path_ + "epos.inics"); // lhcparameters adds ".lhc" - strcpy(::epos::fname_.fncs, CS.data); - ::epos::nfname_.nfncs = CS.length; - - // dummy event (prepare commons) - initializeEventLab(Code::Iron, Iron::nucleus_A, Iron::nucleus_Z, Code::Argon, - Argon::nucleus_A, Argon::nucleus_Z, 100_GeV); - } - - inline void Interaction::initializeEventCoM(Code const idBeam, int const iBeamA, - int const iBeamZ, Code const idTarget, - int const iTargetA, int const iTargetZ, - HEPEnergyType const Ecm) const { - CORSIKA_LOGGER_TRACE(logger_, - "initialize event in CoM frame!" - " Ecm={}", - Ecm); - ::epos::lept1_.engy = -1.; - ::epos::enrgy_.ecms = -1.; - ::epos::enrgy_.elab = -1.; - ::epos::enrgy_.ekin = -1.; - ::epos::hadr1_.pnll = -1.; - - ::epos::enrgy_.ecms = Ecm / 1_GeV; // -> c.m.s. frame - - CORSIKA_LOGGER_TRACE(logger_, - "inside EPOS: " - "Ecm={}, " - "Elab={}", - ::epos::enrgy_.ecms, ::epos::enrgy_.elab); - - configureParticles(idBeam, iBeamA, iBeamZ, idTarget, iTargetA, iTargetZ); - ::epos::ainit_(); - } - - inline void Interaction::initializeEventLab(Code const idBeam, int const iBeamA, - int const iBeamZ, Code const idTarget, - int const iTargetA, int const iTargetZ, - HEPEnergyType const Plab) const { - CORSIKA_LOGGER_TRACE(logger_, - "initialize event in lab. frame!" - " Plab per nuc={} GeV", - Plab / 1_GeV); - ::epos::lept1_.engy = -1.; - ::epos::enrgy_.ecms = -1.; - ::epos::enrgy_.elab = -1.; - ::epos::enrgy_.ekin = -1.; - ::epos::hadr1_.pnll = -1.; - - // hadron-nucleon momentum - ::epos::hadr1_.pnll = float(Plab / 1_GeV); // -> lab frame - - CORSIKA_LOGGER_TRACE(logger_, - "inside EPOS: " - "Ecm={}, " - "Elab={}, " - "Pnll={}", - ::epos::enrgy_.ecms, ::epos::enrgy_.elab, ::epos::hadr1_.pnll); - - configureParticles(idBeam, iBeamA, iBeamZ, idTarget, iTargetA, iTargetZ); - ::epos::ainit_(); - } - - inline void Interaction::configureParticles(Code const idBeam, int const iBeamA, - int const iBeamZ, Code const idTarget, - int const iTargetA, - int const iTargetZ) const { - CORSIKA_LOGGER_TRACE(logger_, - "setting " - "Beam={}, " - "BeamA={}, " - "BeamZ={}, " - "Target={}, " - "TargetA={}, " - "TargetZ={} ", - idBeam, iBeamA, iBeamZ, idTarget, iTargetA, iTargetZ); - - if (is_nucleus(idBeam)) { - ::epos::hadr25_.idprojin = convertToEposRaw(Code::Proton); - ::epos::nucl1_.laproj = iBeamZ; - ::epos::nucl1_.maproj = iBeamA; - } else { - ::epos::hadr25_.idprojin = convertToEposRaw(idBeam); - ::epos::nucl1_.laproj = -1; - ::epos::nucl1_.maproj = 1; - } - - if (is_nucleus(idTarget)) { - ::epos::hadr25_.idtargin = convertToEposRaw(Code::Proton); - ::epos::nucl1_.matarg = iTargetA; - ::epos::nucl1_.latarg = iTargetZ; - } else if (idTarget == Code::Proton || idTarget == Code::Hydrogen) { - ::epos::hadr25_.idtargin = convertToEposRaw(Code::Proton); - ::epos::nucl1_.matarg = 1; - ::epos::nucl1_.latarg = -1; - } else if (idTarget == Code::Neutron) { - ::epos::hadr25_.idtargin = convertToEposRaw(Code::Neutron); - ::epos::nucl1_.matarg = 1; - ::epos::nucl1_.latarg = -1; - } else { - throw std::runtime_error("Epos: target outside range!"); - } - CORSIKA_LOGGER_TRACE(logger_, - "inside EPOS: " - "Id beam={}, " - "Z beam={}, " - "A beam={}, " - "XS beam={}, " - "Id target={}, " - "Z target={}, " - "A target={}, " - "XS target={} ", - ::epos::hadr25_.idprojin, ::epos::nucl1_.laproj, - ::epos::nucl1_.maproj, ::epos::had10_.iclpro, - ::epos::hadr25_.idtargin, ::epos::nucl1_.latarg, - ::epos::nucl1_.matarg, ::epos::had10_.icltar); - } - - inline Interaction::~Interaction() { CORSIKA_LOGGER_DEBUG(logger_, "n={} ", count_); } - - inline std::tuple<CrossSectionType, CrossSectionType> Interaction::calcCrossSectionCoM( - Code const BeamId, int const BeamA, int const BeamZ, Code const TargetId, - int const TargetA, int const TargetZ, const HEPEnergyType EnergyCOM) const { - CORSIKA_LOGGER_DEBUG(logger_, - "calcCrossSection: input:" - " beamId={}, beamA={}, beamZ={}" - " target={}, targetA={}, targetZ={}" - " Ecm={:4.3f} GeV,", - BeamId, BeamA, BeamZ, TargetId, TargetA, TargetZ, - EnergyCOM / 1_GeV); - - const int iBeam = corsika::epos::getEposXSCode( - BeamId); // 0 (can not interact, 1: proton-like, 2: pion-like, 3:kaon-like) - if (!iBeam) - throw std::runtime_error( - "calcCrossSectionCoM: interaction of beam hadron not defined in " - "Epos!"); - - CORSIKA_LOGGER_TRACE(logger_, - "projectile cross section type={} " - "(0: cannot interact, 1:pion, 2:baryon, 3:kaon)", - iBeam); - // reset beam particle // (1: pion-like, 2: proton-like, 3:kaon-like) - if (iBeam == 1) - initializeEventCoM(Code::PiPlus, BeamA, BeamZ, TargetId, TargetA, TargetZ, - EnergyCOM); - else if (iBeam == 2) - initializeEventCoM(Code::Proton, BeamA, BeamZ, TargetId, TargetA, TargetZ, - EnergyCOM); - else if (iBeam == 3) - initializeEventCoM(Code::KPlus, BeamA, BeamZ, TargetId, TargetA, TargetZ, - EnergyCOM); - else - throw std::runtime_error( - "calcCrossSectionCoM: interaction of beam hadron not defined in " - "Epos!"); - - double sigProd, sigEla = 0; - float sigTot1, sigProd1, sigCut1 = 0; - if (!is_nucleus(TargetId) && !is_nucleus(BeamId)) { - sigProd = ::epos::hadr5_.sigine; - sigEla = ::epos::hadr5_.sigela; - } else { - // calculate from model, SLOW: - float sigQEla1 = 0; // target fragmentation/excitation - ::epos::crseaaepos_(sigTot1, sigProd1, sigCut1, sigQEla1); - sigProd = sigProd1; - // sigEla not properly defined here - } - CORSIKA_LOGGER_DEBUG(logger_, - "calcCrossSectionCoM: output:" - " sigProd={} mb," - " sigEla={} mb", - sigProd, sigEla); - - return std::make_tuple(sigProd * 1_mb, sigEla * 1_mb); - } - - inline std::tuple<corsika::CrossSectionType, corsika::CrossSectionType> - Interaction::readCrossSectionTableLab(Code const BeamId, int const BeamA, - int const BeamZ, Code const TargetId, - HEPEnergyType const EnergyLab) const { - CORSIKA_LOGGER_DEBUG(logger_, - "readCrossSectionTableLab: input: " - "beamId={}, " - "beamA={}, " - "beamZ={} " - "targetId={}, " - "ELab={:4.3f} GeV,", - BeamId, BeamA, BeamZ, TargetId, EnergyLab / 1_GeV); - - // read cross section from epos internal tables - int Abeam = 0; - float Ekin = -1; - - if (is_nucleus(BeamId)) { - Abeam = BeamA; - // kinetic energy per nucleon - Ekin = (EnergyLab / Abeam - constants::nucleonMass) / 1_GeV; - } else { - ::epos::hadr2_.idproj = convertToEposRaw(BeamId); - int const iBeam = corsika::epos::getEposXSCode( - BeamId); // 0 (can not interact, 1: pion-like, 2: proton-like, 3:kaon-like) - CORSIKA_LOGGER_TRACE(logger_, - "projectile cross section type={} " - "(0: cannot interact, 1:pion, 2:baryon, 3:kaon)", - iBeam); - - ::epos::had10_.iclpro = iBeam; - Abeam = 1; - Ekin = (EnergyLab - get_mass(BeamId)) / 1_GeV; - } - if (Ekin < 0) { - CORSIKA_LOGGER_ERROR(logger_, - "Negative kinetic energy!" - "Ekin={}", - Ekin); - throw std::runtime_error("Epos cross section failed! Negative kinetic energy!"); - } - - int Atarget = 1; - if (is_nucleus(TargetId)) { Atarget = get_nucleus_A(TargetId); } - - int iMode = 3; // 0: air, >0 not air - - CORSIKA_LOGGER_DEBUG(logger_, - "inside Epos " - "beamId={}, beamXS={}", - ::epos::hadr2_.idproj, ::epos::had10_.iclpro); - - // cross section from table, FAST - float sigProdEpos = ::epos::eposcrse_(Ekin, Abeam, Atarget, iMode); - // sig-el from analytic calculation, no fast - float sigElaEpos = ::epos::eposelacrse_(Ekin, Abeam, Atarget, iMode); - - return std::make_tuple(sigProdEpos * 1_mb, sigElaEpos * 1_mb); - } - - inline std::tuple<corsika::CrossSectionType, corsika::CrossSectionType> - Interaction::getCrossSectionLab(corsika::Code const BeamId, int const BeamA, - int const BeamZ, corsika::Code const TargetId, - int const TargetA, int const TargetZ, - const corsika::HEPEnergyType EnergyLab) const { - CORSIKA_LOGGER_DEBUG(logger_, - "getCrossSectionLab: input:" - " beamId={}, beamA={}, beamZ={}" - " target={}, targetA={}, targetZ={}" - " ELab={:4.3f} GeV,", - BeamId, BeamA, BeamZ, TargetId, TargetA, TargetZ, - EnergyLab / 1_GeV); - return readCrossSectionTableLab(BeamId, BeamA, BeamZ, TargetId, EnergyLab); - } - - template <> - inline corsika::GrammageType Interaction::getInteractionLength( - SetupParticle const& projectile) const { - - const corsika::Code corsikaBeamId = projectile.getPID(); - const bool kInteraction = corsika::epos::canInteract(corsikaBeamId); - CORSIKA_LOGGER_DEBUG(logger_, - "InteractionLength: input: \n" - " energy: {} GeV " - " beam can interact: {} " - " beam pid: {}", - projectile.getEnergy() / 1_GeV, kInteraction, - projectile.getPID()); - - if (kInteraction) { - - // define projectile nuclei - int beamA = 1; - int beamZ = 1; - if (is_nucleus(corsikaBeamId)) { - beamA = get_nucleus_A(corsikaBeamId); - beamZ = get_nucleus_Z(corsikaBeamId); - } - - // get target from environment - MomentumVector const& pLab = projectile.getMomentum(); - CoordinateSystemPtr const& labCS = pLab.getCoordinateSystem(); - - // assume target is at rest!! - MomentumVector pTarget(labCS, {0_GeV, 0_GeV, 0_GeV}); - - // total momentum and energy - HEPEnergyType Elab = projectile.getEnergy() + constants::nucleonMass; - - auto const* currentNode = projectile.getNode(); - const auto& mediumComposition = - currentNode->getModelProperties().getNuclearComposition(); - - si::CrossSectionType weightedProdCrossSection = mediumComposition.getWeightedSum( - [=](corsika::Code targetID) -> si::CrossSectionType { - return std::get<0>(this->getCrossSectionLab(corsikaBeamId, beamA, beamZ, - targetID, get_nucleus_A(targetID), - get_nucleus_Z(targetID), Elab)); - }); - - CORSIKA_LOGGER_DEBUG(logger_, "InteractionLength: weighted CrossSection (mb): {} ", - weightedProdCrossSection / 1_mb); - - // calculate interaction length in medium - GrammageType const int_length = mediumComposition.getAverageMassNumber() * - constants::u / weightedProdCrossSection; - CORSIKA_LOGGER_DEBUG(logger_, "interaction length (g/cm2): {} ", - int_length / (0.001_kg) * 1_cm * 1_cm); - - return int_length; - } - - return std::numeric_limits<double>::infinity() * 1_g / (1_cm * 1_cm); - } - - template <typename TSecondaryView> - inline void Interaction::doInteraction(TSecondaryView& view) { - - auto const projectile = view.getProjectile(); - auto const corsikaBeamId = projectile.getPID(); - - CORSIKA_LOGGER_DEBUG(logger_, "doInteraction: {} interaction, Elab={} ", - corsikaBeamId, projectile.getEnergy()); - - if (corsika::epos::canInteract(corsikaBeamId)) { - count_ = count_ + 1; - // position and time of interaction, not used in Epos - Point const pOrig = projectile.getPosition(); - TimeType const tOrig = projectile.getTime(); - - // define projectile - HEPEnergyType const eProjectileLab = projectile.getEnergy(); - auto const pProjectileLab = projectile.getMomentum(); - auto const projectileMomentum = pProjectileLab.getNorm(); - CoordinateSystemPtr const& originalCS = pProjectileLab.getCoordinateSystem(); - - // epos frame with z along the projectile direction - CoordinateSystemPtr const zAxisFrame = make_rotationToZ(originalCS, pProjectileLab); - - int beamA = 1; - int beamZ = 1; - if (is_nucleus(corsikaBeamId)) { - beamA = get_nucleus_A(corsikaBeamId); - beamZ = get_nucleus_Z(corsikaBeamId); - CORSIKA_LOGGER_DEBUG(logger_, "A={}, Z={} ", beamA, beamZ); - } - - HEPEnergyType const projectileMomentumLabPerNucleon = projectileMomentum / beamA; - - // define target - - // sample target mass number - auto const* currentNode = projectile.getNode(); - auto const& mediumComposition = - currentNode->getModelProperties().getNuclearComposition(); - // get cross sections for target materials - /* - Here we read the cross section from the interaction model again, - should be passed from getInteractionLength if possible - */ - //#warning reading interaction cross section again, should not be necessary - auto const& compVec = mediumComposition.getComponents(); - std::vector<CrossSectionType> cross_section_of_components(compVec.size()); - - for (size_t i = 0; i < compVec.size(); ++i) { - auto const targetId = compVec[i]; - [[maybe_unused]] auto const [sigProd, sigEla] = getCrossSectionLab( - corsikaBeamId, beamA, beamZ, targetId, get_nucleus_A(targetId), - get_nucleus_Z(targetId), eProjectileLab); - cross_section_of_components[i] = sigProd; - } - - const auto targetCode = - mediumComposition.sampleTarget(cross_section_of_components, RNG_); - CORSIKA_LOGGER_DEBUG(logger_, "target selected: {} ", targetCode); - - // // from corsika7 interface - // // NEXLNK-part - int targetA = 1; - int targetZ = 1; - if (is_nucleus(targetCode)) { - targetA = get_nucleus_A(targetCode); - targetZ = get_nucleus_Z(targetCode); - } - initializeEventLab(corsikaBeamId, beamA, beamZ, targetCode, targetA, targetZ, - projectileMomentumLabPerNucleon); - - // create event - int iarg = 1; - ::epos::aepos_(iarg); - - ::epos::afinal_(); - - if (epos_listing_) { - char nam[9] = "EPOSLHC&"; - ::epos::alistf_(nam, 9); - } - - // NSTORE-part - - MomentumVector Plab_final(originalCS, {0.0_GeV, 0.0_GeV, 0.0_GeV}); - HEPEnergyType Elab_final = 0_GeV; - - // secondaries - EposStack es; - CORSIKA_LOGGER_DEBUG(logger_, "number of particles: {}", es.getSize()); - for (auto& psec : es) { - if (!psec.isFinal()) continue; - - auto momentum = psec.getMomentum(zAxisFrame); - - momentum.rebase(originalCS); // transform back into standard lab frame - - EposCode const eposId = psec.getPID(); - Code const pid = corsika::epos::convertFromEpos(eposId); - CORSIKA_LOGGER_TRACE(logger_, - " id= {}" - " p= {}", - pid, momentum.getComponents() / 1_GeV); - if (!is_nucleus(pid)) { - auto pnew = view.addSecondary(std::make_tuple(pid, momentum, pOrig, tOrig)); - Plab_final += pnew.getMomentum(); - Elab_final += pnew.getEnergy(); - } else { - unsigned int A = 0; - unsigned int Z = 0; - if (pid == Code::Deuterium) { - A = 2; - Z = 1; - } else if (pid == Code::Tritium) { - A = 3; - Z = 1; - } else if (pid == Code::Helium) { - A = 4; - Z = 2; - } else { - Z = get_nucleus_Z(eposId); - A = get_nucleus_Z(eposId); - } - auto pnew = view.addSecondary( - std::make_tuple(get_nucleus_code(A, Z), momentum, pOrig, tOrig)); - Plab_final += pnew.getMomentum(); - Elab_final += pnew.getEnergy(); - } - } - CORSIKA_LOGGER_DEBUG( - logger_, - "conservation (all GeV): Ecm_final= n/a" /* << Ecm_final / 1_GeV*/ - ", Elab_final={}" - ", Plab_final={}", - Elab_final / 1_GeV, (Plab_final / 1_GeV).getComponents()); - } else - CORSIKA_LOGGER_WARN( - logger_, "Projectile not configured for interaction! This is likely an error!"); - } -} // namespace corsika::epos diff --git a/corsika/detail/modules/epos/InteractionModel.inl b/corsika/detail/modules/epos/InteractionModel.inl new file mode 100644 index 0000000000000000000000000000000000000000..039d9635de189d5c6a96309e6a96b4bd07365dd7 --- /dev/null +++ b/corsika/detail/modules/epos/InteractionModel.inl @@ -0,0 +1,493 @@ +/* + * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu + * + * This software is distributed under the terms of the GNU General Public + * Licence version 3 (GPL Version 3). See file LICENSE for a full version of + * the license. + */ + +#pragma once + +#include <corsika/modules/epos/InteractionModel.hpp> +#include <corsika/modules/epos/EposStack.hpp> + +#include <corsika/framework/geometry/Point.hpp> + +#include <corsika/framework/utility/COMBoost.hpp> +#include <corsika/framework/utility/CorsikaData.hpp> + +#include <epos.hpp> + +#include <string> +#include <tuple> + +namespace corsika::epos { + + inline InteractionModel::InteractionModel(std::string const& dataPath, + bool const epos_printout_on) + : data_path_(dataPath) + , epos_listing_(epos_printout_on) { + // initialize Eposlhc + if (!isInitialized_) { + isInitialized_ = true; + if (dataPath == "") { + data_path_ = (std::string(corsika_data("EPOS").c_str()) + "/").c_str(); + } + initialize(); + } + setParticlesStable(); + } + + inline void InteractionModel::setParticlesStable() const { + CORSIKA_LOGGER_DEBUG(logger_, + "set all particles known to CORSIKA stable inside EPOS.."); + for (auto& p : get_all_particles()) { + if (!is_hadron(p)) continue; + int const eid = convertToEposRaw(p); + if (eid != 0) { + ::epos::nodcy_.nrnody = ::epos::nodcy_.nrnody + 1; + ::epos::nodcy_.nody[::epos::nodcy_.nrnody - 1] = eid; + } + } + } + + inline bool InteractionModel::isValid(Code const projectileId, Code const targetId, + HEPEnergyType const sqrtS) const { + //! eposlhc only accepts nuclei with X<=A<=Y as targets, or protons aka Hydrogen or + //! neutrons (p,n == nucleon) + if (!is_nucleus(targetId) && targetId != Code::Neutron && targetId != Code::Proton) { + return false; + } + if (is_nucleus(targetId) && (get_nucleus_A(targetId) >= maxTargetMassNumber_)) { + return false; + } + if ((minEnergyCoM_ > sqrtS) || (sqrtS > maxEnergyCoM_)) { return false; } + if (!epos::canInteract(projectileId)) { return false; } + return true; + } + + inline void InteractionModel::initialize() const { + + CORSIKA_LOGGER_DEBUG(logger_, "initializing..."); + + // corsika7 ini + int iarg = 0; + ::epos::aaset_(iarg); + + // debug output settings + ::epos::prnt1_.ish = 0; // debug level in epos, 0: off, 6: medium output + ::epos::prnt3_.iwseed = 0; // 1: printout seeds, 0: off + ::epos::files_.ifch = 6; // output unit, 6: screen + + // dummy set seeds for random number generator in epos. need to fool epos checks... + // we will use external generator + ::epos::cseed_.seedi = 1; + ::epos::cseed_.seedj = 1; + ::epos::cseed_.seedc = 1; + + ::epos::enrgy_.egymin = minEnergyCoM_ / 1_GeV; // 6.; + ::epos::enrgy_.egymax = maxEnergyCoM_ / 1_GeV; // 2.e6; + + ::epos::lhcparameters_(); + + ::epos::hadr6_.isigma = 0; // do not show cross section + ::epos::hadr6_.isetcs = 3; /* !option to obtain pomeron parameters + ! 0.....determine parameters but do not use Kfit + ! 1.....determine parameters and use Kfit + ! else..get from table + ! should be sufficiently detailed + ! say iclegy1=1,iclegy2=99 + ! table is always done, more or less detailed!!! + !and option to use cross section tables + ! 2....tabulation + ! 3....simulation + */ + ::epos::cjinti_.ionudi = + 1; // !include quasi elastic events but strict calculation of xs + ::epos::cjinti_.iorsce = 0; // !color exchange turned on(1) or off(0) + ::epos::cjinti_.iorsdf = 3; // !droplet formation turned on(>0) or off(0) + ::epos::cjinti_.iorshh = 0; // !other hadron-hadron int. turned on(1) or off(0) + + ::epos::othe1_.istore = 0; // do not produce epos output file + ::epos::nucl6_.infragm = + 2; // 0: keep free nucleons in fragmentation,1: one fragment, 2: fragmentation + + ::epos::othe2_.iframe = 12; // lab frame, target at rest + + // set paths to tables in corsika data + ::epos::datadir BASE(data_path_); + strcpy(::epos::fname_.fnnx, BASE.data); + ::epos::nfname_.nfnnx = BASE.length; + + ::epos::datadir TL(data_path_ + "epos.initl"); + strcpy(::epos::fname_.fnii, TL.data); + ::epos::nfname_.nfnii = TL.length; + + ::epos::datadir EV(data_path_ + "epos.iniev"); + strcpy(::epos::fname_.fnie, EV.data); + ::epos::nfname_.nfnie = EV.length; + + ::epos::datadir RJ(data_path_ + "epos.inirj"); // lhcparameters adds ".lhc" + strcpy(::epos::fname_.fnrj, RJ.data); + ::epos::nfname_.nfnrj = RJ.length; + + ::epos::datadir CS(data_path_ + "epos.inics"); // lhcparameters adds ".lhc" + strcpy(::epos::fname_.fncs, CS.data); + ::epos::nfname_.nfncs = CS.length; + + // initialiazes maximum energy and mass + initializeEventLab(Code::Lead, Lead::nucleus_A, Lead::nucleus_Z, Code::Lead, + Lead::nucleus_A, Lead::nucleus_Z, 1_TeV); + } + + inline void InteractionModel::initializeEventCoM(Code const idBeam, int const iBeamA, + int const iBeamZ, Code const idTarget, + int const iTargetA, int const iTargetZ, + HEPEnergyType const Ecm) const { + CORSIKA_LOGGER_TRACE(logger_, + "initialize event in CoM frame!" + " Ecm={}", + Ecm); + ::epos::lept1_.engy = -1.; + ::epos::enrgy_.ecms = -1.; + ::epos::enrgy_.elab = -1.; + ::epos::enrgy_.ekin = -1.; + ::epos::hadr1_.pnll = -1.; + + ::epos::enrgy_.ecms = Ecm / 1_GeV; // -> c.m.s. frame + + CORSIKA_LOGGER_TRACE(logger_, "inside EPOS: Ecm={}, Elab={}", ::epos::enrgy_.ecms, + ::epos::enrgy_.elab); + + configureParticles(idBeam, iBeamA, iBeamZ, idTarget, iTargetA, iTargetZ); + ::epos::ainit_(); + } + + inline void InteractionModel::initializeEventLab(Code const idBeam, int const iBeamA, + int const iBeamZ, Code const idTarget, + int const iTargetA, int const iTargetZ, + HEPEnergyType const Plab) const { + CORSIKA_LOGGER_TRACE(logger_, + "initialize event in lab. frame!" + " Plab per nuc={} GeV", + Plab / 1_GeV); + ::epos::lept1_.engy = -1.; + ::epos::enrgy_.ecms = -1.; + ::epos::enrgy_.elab = -1.; + ::epos::enrgy_.ekin = -1.; + ::epos::hadr1_.pnll = -1.; + + // hadron-nucleon momentum + ::epos::hadr1_.pnll = float(Plab / 1_GeV); // -> lab frame + + CORSIKA_LOGGER_TRACE(logger_, "inside EPOS: Ecm={}, Elab={}, Pnll={}", + ::epos::enrgy_.ecms, ::epos::enrgy_.elab, ::epos::hadr1_.pnll); + + configureParticles(idBeam, iBeamA, iBeamZ, idTarget, iTargetA, iTargetZ); + ::epos::ainit_(); + } + + inline void InteractionModel::configureParticles(Code const idBeam, int const iBeamA, + int const iBeamZ, Code const idTarget, + int const iTargetA, + int const iTargetZ) const { + CORSIKA_LOGGER_TRACE(logger_, + "setting " + "Beam={}, " + "BeamA={}, " + "BeamZ={}, " + "Target={}, " + "TargetA={}, " + "TargetZ={} ", + idBeam, iBeamA, iBeamZ, idTarget, iTargetA, iTargetZ); + + if (is_nucleus(idBeam)) { + ::epos::hadr25_.idprojin = convertToEposRaw(Code::Proton); + ::epos::nucl1_.laproj = iBeamZ; + ::epos::nucl1_.maproj = iBeamA; + } else { + ::epos::hadr25_.idprojin = convertToEposRaw(idBeam); + ::epos::nucl1_.laproj = -1; + ::epos::nucl1_.maproj = 1; + } + + if (is_nucleus(idTarget)) { + ::epos::hadr25_.idtargin = convertToEposRaw(Code::Proton); + ::epos::nucl1_.matarg = iTargetA; + ::epos::nucl1_.latarg = iTargetZ; + } else if (idTarget == Code::Proton || idTarget == Code::Hydrogen) { + ::epos::hadr25_.idtargin = convertToEposRaw(Code::Proton); + ::epos::nucl1_.matarg = 1; + ::epos::nucl1_.latarg = -1; + } else if (idTarget == Code::Neutron) { + ::epos::hadr25_.idtargin = convertToEposRaw(Code::Neutron); + ::epos::nucl1_.matarg = 1; + ::epos::nucl1_.latarg = -1; + } + + CORSIKA_LOGGER_TRACE(logger_, + "inside EPOS: " + "Id beam={}, " + "Z beam={}, " + "A beam={}, " + "XS beam={}, " + "Id target={}, " + "Z target={}, " + "A target={}, " + "XS target={} ", + ::epos::hadr25_.idprojin, ::epos::nucl1_.laproj, + ::epos::nucl1_.maproj, ::epos::had10_.iclpro, + ::epos::hadr25_.idtargin, ::epos::nucl1_.latarg, + ::epos::nucl1_.matarg, ::epos::had10_.icltar); + } + + inline InteractionModel::~InteractionModel() { + CORSIKA_LOGGER_DEBUG(logger_, "n={} ", count_); + } + + inline std::tuple<CrossSectionType, CrossSectionType> + InteractionModel::calcCrossSectionCoM(Code const BeamId, int const BeamA, + int const BeamZ, Code const TargetId, + int const TargetA, int const TargetZ, + const HEPEnergyType EnergyCOM) const { + CORSIKA_LOGGER_DEBUG(logger_, + "calcCrossSection: input:" + " beamId={}, beamA={}, beamZ={}" + " target={}, targetA={}, targetZ={}" + " Ecm={:4.3f} GeV,", + BeamId, BeamA, BeamZ, TargetId, TargetA, TargetZ, + EnergyCOM / 1_GeV); + + const int iBeam = epos::getEposXSCode( + BeamId); // 0 (can not interact, 1: proton-like, 2: pion-like, 3:kaon-like) + + CORSIKA_LOGGER_TRACE(logger_, + "projectile cross section type={} " + "(0: cannot interact, 1:pion, 2:baryon, 3:kaon)", + iBeam); + // reset beam particle // (1: pion-like, 2: proton-like, 3:kaon-like) + if (iBeam == 1) + initializeEventCoM(Code::PiPlus, BeamA, BeamZ, TargetId, TargetA, TargetZ, + EnergyCOM); + else if (iBeam == 2) + initializeEventCoM(Code::Proton, BeamA, BeamZ, TargetId, TargetA, TargetZ, + EnergyCOM); + else if (iBeam == 3) + initializeEventCoM(Code::KPlus, BeamA, BeamZ, TargetId, TargetA, TargetZ, + EnergyCOM); + + double sigProd, sigEla = 0; + float sigTot1, sigProd1, sigCut1 = 0; + if (!is_nucleus(TargetId) && !is_nucleus(BeamId)) { + sigProd = ::epos::hadr5_.sigine; + sigEla = ::epos::hadr5_.sigela; + } else { + // calculate from model, SLOW: + float sigQEla1 = 0; // target fragmentation/excitation + ::epos::crseaaepos_(sigTot1, sigProd1, sigCut1, sigQEla1); + sigProd = sigProd1; + // sigEla not properly defined here + } + CORSIKA_LOGGER_DEBUG(logger_, + "calcCrossSectionCoM: output:" + " sigProd={} mb," + " sigEla={} mb", + sigProd, sigEla); + + return std::make_tuple(sigProd * 1_mb, sigEla * 1_mb); + } + + inline std::tuple<CrossSectionType, CrossSectionType> + InteractionModel::readCrossSectionTableLab(Code const BeamId, int const BeamA, + int const BeamZ, Code const TargetId, + HEPEnergyType const EnergyLab) const { + CORSIKA_LOGGER_DEBUG(logger_, + "readCrossSectionTableLab: input: " + "beamId={}, " + "beamA={}, " + "beamZ={} " + "targetId={}, " + "ELab={:4.3f} GeV,", + BeamId, BeamA, BeamZ, TargetId, EnergyLab / 1_GeV); + + // read cross section from epos internal tables + int Abeam = 0; + float Ekin = -1; + + if (is_nucleus(BeamId)) { + Abeam = BeamA; + // kinetic energy per nucleon + Ekin = (EnergyLab / Abeam - constants::nucleonMass) / 1_GeV; + } else { + ::epos::hadr2_.idproj = convertToEposRaw(BeamId); + int const iBeam = epos::getEposXSCode( + BeamId); // 0 (can not interact, 1: pion-like, 2: proton-like, 3:kaon-like) + CORSIKA_LOGGER_TRACE(logger_, + "projectile cross section type={} " + "(0: cannot interact, 1:pion, 2:baryon, 3:kaon)", + iBeam); + + ::epos::had10_.iclpro = iBeam; + Abeam = 1; + Ekin = (EnergyLab - get_mass(BeamId)) / 1_GeV; + } + + int Atarget = 1; + if (is_nucleus(TargetId)) { Atarget = get_nucleus_A(TargetId); } + + int iMode = 3; // 0: air, >0 not air + + CORSIKA_LOGGER_DEBUG(logger_, + "inside Epos " + "beamId={}, beamXS={}", + ::epos::hadr2_.idproj, ::epos::had10_.iclpro); + + // cross section from table, FAST + float sigProdEpos = ::epos::eposcrse_(Ekin, Abeam, Atarget, iMode); + // sig-el from analytic calculation, no fast + float sigElaEpos = ::epos::eposelacrse_(Ekin, Abeam, Atarget, iMode); + + return std::make_tuple(sigProdEpos * 1_mb, sigElaEpos * 1_mb); + } + + inline std::tuple<CrossSectionType, CrossSectionType> + InteractionModel::getCrossSectionInelEla(Code const projectileId, Code const targetId, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) const { + auto const sqrtS2 = (projectileP4 + targetP4).getNormSqr(); + auto const sqrtS = sqrt(sqrtS2); + + if (!isValid(projectileId, targetId, sqrtS)) { + return {CrossSectionType::zero(), CrossSectionType::zero()}; + } + HEPEnergyType const Elab = (sqrtS2 - static_pow<2>(get_mass(projectileId)) - + static_pow<2>(get_mass(targetId))) / + (2 * get_mass(targetId)); + int beamA = 1; + int beamZ = 1; + if (is_nucleus(projectileId)) { + beamA = get_nucleus_A(projectileId); + beamZ = get_nucleus_Z(projectileId); + } + + CORSIKA_LOGGER_DEBUG(logger_, + "getCrossSectionLab: input:" + " beamId={}, beamA={}, beamZ={}" + " target={}" + " ELab={:4.3f} GeV, sqrtS={}", + projectileId, beamA, beamZ, targetId, Elab / 1_GeV, + sqrtS / 1_GeV); + return readCrossSectionTableLab(projectileId, beamA, beamZ, targetId, Elab); + } + + template <typename TSecondaryView> + inline void InteractionModel::doInteraction(TSecondaryView& view, + Code const projectileId, + Code const targetId, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) { + + count_ = count_ + 1; + + // define projectile + // define projectile, in lab frame + auto const sqrtS2 = (projectileP4 + targetP4).getNormSqr(); + auto const sqrtS = sqrt(sqrtS2); + if (!isValid(projectileId, targetId, sqrtS)) { + throw std::runtime_error("invalid projectile/target/energy combination."); + } + HEPEnergyType const Elab = (sqrtS2 - static_pow<2>(get_mass(projectileId)) - + static_pow<2>(get_mass(targetId))) / + (2 * get_mass(targetId)); + + // system of initial-state + COMBoost const boost(projectileP4, targetP4); + + auto const& originalCS = boost.getOriginalCS(); + auto const& csPrime = + boost.getRotatedCS(); // z is along the CM motion (projectile, in Cascade) + + HEPMomentumType const pLabMag = + sqrt((Elab - get_mass(projectileId)) * (Elab + get_mass(projectileId))); + MomentumVector pLab(csPrime, {0_eV, 0_eV, pLabMag}); + + // internal EPOS lab system + COMBoost const boostInternal({Elab, pLab}, get_mass(targetId)); + + CORSIKA_LOGGER_DEBUG(logger_, "doInteraction: {} interaction, Elab={} ", projectileId, + Elab); + + int beamA = 1; + int beamZ = 1; + if (is_nucleus(projectileId)) { + beamA = get_nucleus_A(projectileId); + beamZ = get_nucleus_Z(projectileId); + CORSIKA_LOGGER_DEBUG(logger_, "A={}, Z={} ", beamA, beamZ); + } + + HEPMomentumType const projectileMomentumLabPerNucleon = pLabMag / beamA; + + // // from corsika7 interface + // // NEXLNK-part + int targetA = 1; + int targetZ = 1; + if (is_nucleus(targetId)) { + targetA = get_nucleus_A(targetId); + targetZ = get_nucleus_Z(targetId); + } + initializeEventLab(projectileId, beamA, beamZ, targetId, targetA, targetZ, + projectileMomentumLabPerNucleon); + + // create event + int iarg = 1; + ::epos::aepos_(iarg); + ::epos::afinal_(); + + if (epos_listing_) { // LCOV_EXCL_START + char nam[9] = "EPOSLHC&"; + ::epos::alistf_(nam, 9); + } // LCOV_EXCL_STOP + + // NSTORE-part + + MomentumVector Plab_final(originalCS, {0.0_GeV, 0.0_GeV, 0.0_GeV}); + HEPEnergyType Elab_final = 0_GeV; + + // position and time of interaction, not used in QgsjetII + auto const& projectile = view.getProjectile(); + Point const& pOrig = projectile.getPosition(); + TimeType const tOrig = projectile.getTime(); + + // secondaries + EposStack es; + CORSIKA_LOGGER_DEBUG(logger_, "number of particles: {}", es.getSize()); + for (auto& psec : es) { + if (!psec.isFinal()) continue; + + auto momentum = psec.getMomentum(csPrime); + // this is not "CoM" here, but rather the system defined by projectile+target, + // which in Cascade-mode is already lab + auto const P4com = boostInternal.toCoM(FourVector{psec.getEnergy(), momentum}); + auto const P4output = boost.fromCoM(P4com); + auto p3output = P4output.getSpaceLikeComponents(); + p3output.rebase(originalCS); // transform back into standard lab frame + + EposCode const eposId = psec.getPID(); + Code const pid = epos::convertFromEpos(eposId); + CORSIKA_LOGGER_TRACE(logger_, + " id= {}" + " p= {}", + pid, p3output.getComponents() / 1_GeV); + + auto pnew = view.addSecondary(std::make_tuple(pid, p3output, pOrig, tOrig)); + Plab_final += pnew.getMomentum(); + Elab_final += pnew.getEnergy(); + } + CORSIKA_LOGGER_DEBUG( + logger_, + "conservation (all GeV): Ecm_final= n/a" /* << Ecm_final / 1_GeV*/ + ", Elab_final={}" + ", Plab_final={}", + Elab_final / 1_GeV, (Plab_final / 1_GeV).getComponents()); + } +} // namespace corsika::epos diff --git a/corsika/detail/modules/proposal/Interaction.inl b/corsika/detail/modules/proposal/Interaction.inl index c450594146f13a7afaa809c99d0f3354d9427b02..e81f9984b7b880ddd208556b087df0d0302b3d59 100644 --- a/corsika/detail/modules/proposal/Interaction.inl +++ b/corsika/detail/modules/proposal/Interaction.inl @@ -39,24 +39,26 @@ namespace corsika::proposal { auto c = p_cross->second(media.at(comp.getHash()), emCut); // Look which interactions take place and build the corresponding - // interaction and secondarie builder. The interaction integral will + // interaction and secondary builder. The interaction integral will // interpolated too and saved in the calc map by a key build out of a hash // of composed of the component and particle code. auto inter_types = PROPOSAL::CrossSectionVector::GetInteractionTypes(c); - calc[std::make_pair(comp.getHash(), code)] = std::make_tuple( + calc_[std::make_pair(comp.getHash(), code)] = std::make_tuple( PROPOSAL::make_secondaries(inter_types, particle[code], media.at(comp.getHash())), PROPOSAL::make_interaction(c, true)); } template <typename TStackView> - inline ProcessReturn Interaction::doInteraction(TStackView& view) { + inline ProcessReturn Interaction::doInteraction(TStackView& view, + Code const projectileId, + FourMomentum const& projectileP4) { auto const projectile = view.getProjectile(); - if (canInteract(projectile.getPID())) { + if (canInteract(projectileId)) { // get or build corresponding calculators - auto c = getCalculator(projectile, calc); + auto c = getCalculator(projectile, calc_); // get the rates of the interaction types for every component. std::uniform_real_distribution<double> distr(0., 1.); @@ -103,14 +105,27 @@ namespace corsika::proposal { } template <typename TParticle> - inline GrammageType Interaction::getInteractionLength(TParticle const& projectile) { - - if (canInteract(projectile.getPID())) { - auto c = getCalculator(projectile, calc); - return std::get<eINTERACTION>(c->second)->MeanFreePath(projectile.getEnergy() / - 1_MeV) * - 1_g / (1_cm * 1_cm); + inline CrossSectionType Interaction::getCrossSection(TParticle const& projectile, + Code const projectileId, + FourMomentum const& projectileP4) { + + // ============================================== + // this block better diappears. RU 26.10.2021 + // + // determine the volume where the particle is (last) known to be + auto const* currentLogicalNode = projectile.getNode(); + NuclearComposition const& composition = + currentLogicalNode->getModelProperties().getNuclearComposition(); + auto const meanMass = composition.getAverageMassNumber() * constants::u; + // ============================================== + + if (canInteract(projectileId)) { + auto c = getCalculator(projectile, calc_); + return meanMass / (std::get<eINTERACTION>(c->second)->MeanFreePath( + projectileP4.getTimeLikeComponent() / 1_MeV) * + 1_g / (1_cm * 1_cm)); } - return std::numeric_limits<double>::infinity() * 1_g / (1_cm * 1_cm); + + return CrossSectionType::zero(); } } // namespace corsika::proposal diff --git a/corsika/detail/modules/pythia8/Interaction.inl b/corsika/detail/modules/pythia8/Interaction.inl index 9e13f4de82834de41ad14f411583169ec811a79f..fe55d587412537f86dff4ca4f0ca0f9dd5ef99ab 100644 --- a/corsika/detail/modules/pythia8/Interaction.inl +++ b/corsika/detail/modules/pythia8/Interaction.inl @@ -80,22 +80,38 @@ namespace corsika::pythia8 { Pythia8::Pythia::particleData.mayDecay(static_cast<int>(get_PDG(pCode)), false); } - inline void Interaction::configureLabFrameCollision(Code const BeamId, - Code const TargetId, + inline bool Interaction::isValid(Code const projectileId, Code const targetId, + HEPEnergyType const sqrtS) const { + + if ((10_GeV > sqrtS) || (sqrtS > 1_PeV)) { return false; } + + if (targetId != Code::Hydrogen && targetId != Code::Neutron && + targetId != Code::Proton) { + return false; + } + + if (is_nucleus(projectileId)) { return false; } + + if (!canInteract(projectileId)) { return false; } + return true; + } + + inline void Interaction::configureLabFrameCollision(Code const projectileId, + Code const targetId, HEPEnergyType const BeamEnergy) { // Pythia configuration of the current event // very clumsy. I am sure this can be done better.. // set beam // beam id for pythia - auto const pdgBeam = static_cast<int>(get_PDG(BeamId)); + auto const pdgBeam = static_cast<int>(get_PDG(projectileId)); std::stringstream stBeam; stBeam << "Beams:idA = " << pdgBeam; Pythia8::Pythia::readString(stBeam.str()); // set target - auto pdgTarget = static_cast<int>(get_PDG(TargetId)); + auto pdgTarget = static_cast<int>(get_PDG(targetId)); // replace hydrogen with proton, otherwise pythia goes into heavy ion mode! - if (TargetId == Code::Hydrogen) pdgTarget = static_cast<int>(get_PDG(Code::Proton)); + if (targetId == Code::Hydrogen) pdgTarget = static_cast<int>(get_PDG(Code::Proton)); std::stringstream stTarget; stTarget << "Beams:idB = " << pdgTarget; Pythia8::Pythia::readString(stTarget.str()); @@ -116,276 +132,133 @@ namespace corsika::pythia8 { // LCOV_EXCL_STOP } - inline bool Interaction::canInteract(Code const pCode) { + inline bool Interaction::canInteract(Code const pCode) const { return pCode == Code::Proton || pCode == Code::Neutron || pCode == Code::AntiProton || pCode == Code::AntiNeutron || pCode == Code::PiMinus || pCode == Code::PiPlus; } - inline std::tuple<CrossSectionType, CrossSectionType> Interaction::getCrossSection( - Code const BeamId, Code const TargetId, HEPEnergyType const CoMenergy) { - // interaction possible in pythia? - if (TargetId == Code::Proton || TargetId == Code::Hydrogen) { - if (canInteract(BeamId) && isValidCoMEnergy(CoMenergy)) { - // input particle PDG - auto const pdgCodeBeam = static_cast<int>(get_PDG(BeamId)); - auto const pdgCodeTarget = static_cast<int>(get_PDG(TargetId)); - double const ecm = CoMenergy / 1_GeV; - - // calculate cross section - sigma_.calc(pdgCodeBeam, pdgCodeTarget, ecm); - if (sigma_.hasSigmaTot()) { - double const sigEla = sigma_.sigmaEl(); - double const sigProd = sigma_.sigmaTot() - sigEla; - - return std::make_tuple(sigProd * (1_fm * 1_fm), sigEla * (1_fm * 1_fm)); - - } else { - // we can't test pythia8 internals, LCOV_EXCL_START - throw std::runtime_error("pythia cross section init failed"); - // we can't test pythia8 internals, LCOV_EXCL_STOP - } - } else { - return std::make_tuple(std::numeric_limits<double>::infinity() * 1_mb, - std::numeric_limits<double>::infinity() * 1_mb); - } - } else { - throw std::runtime_error("invalid target for pythia"); - } - } + inline std::tuple<CrossSectionType, CrossSectionType> + Interaction::getCrossSectionInelEla(Code const projectileId, Code const targetId, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) const { - template <typename TParticle> - inline GrammageType Interaction::getInteractionLength(TParticle const& particle) { + HEPEnergyType const CoMenergy = (projectileP4 + targetP4).getNorm(); - // coordinate system, get global frame of reference - MomentumVector const& pMomentum = particle.getMomentum(); - CoordinateSystemPtr const& labCS = pMomentum.getCoordinateSystem(); + if (!isValid(projectileId, targetId, CoMenergy)) { + return {CrossSectionType::zero(), CrossSectionType::zero()}; + } - Code corsikaBeamId = particle.getPID(); + // input particle PDG + auto const pdgCodeBeam = static_cast<int>(get_PDG(projectileId)); + auto const pdgCodeTarget = static_cast<int>(get_PDG(targetId)); + double const ecm = CoMenergy / 1_GeV; - // beam particles for pythia : 1, 2, 3 for p, pi, k - // read from cross section code table - bool const kInteraction = canInteract(corsikaBeamId); + //! @todo: remove this const_cast, when Pythia8 becomes const-correct! CHECK! + Pythia8::SigmaTotal& sigma = *const_cast<Pythia8::SigmaTotal*>(&sigma_); - // FOR NOW: assume target is at rest - MomentumVector pTarget(labCS, {0_GeV, 0_GeV, 0_GeV}); + // calculate cross section + sigma.calc(pdgCodeBeam, pdgCodeTarget, ecm); + if (sigma.hasSigmaTot()) { + double const sigEla = sigma.sigmaEl(); + double const sigProd = sigma.sigmaTot() - sigEla; - // total momentum and energy - HEPEnergyType Elab = particle.getEnergy() + constants::nucleonMass; - MomentumVector pTotLab(labCS, {0_GeV, 0_GeV, 0_GeV}); - pTotLab += pMomentum; - pTotLab += pTarget; - auto const pTotLabNorm = pTotLab.getNorm(); - // calculate cm. energy - HEPEnergyType const ECoM = sqrt( - (Elab + pTotLabNorm) * (Elab - pTotLabNorm)); // binomial for numerical accuracy + return std::make_tuple(sigProd * (1_fm * 1_fm), sigEla * (1_fm * 1_fm)); - CORSIKA_LOG_DEBUG( - "Interaction: LambdaInt: \n" - " input energy: {} GeV" - " beam can interact: {}" - " beam pid: {}", - particle.getEnergy() / 1_GeV, kInteraction, particle.getPID()); - - // TODO: move limits into variables - if (kInteraction && Elab >= 8.5_GeV && isValidCoMEnergy(ECoM)) { - - // get target from environment - /* - the target should be defined by the Environment, - ideally as full particle object so that the four momenta - and the boosts can be defined.. - */ - auto const* currentNode = particle.getNode(); - auto const mediumComposition = - currentNode->getModelProperties().getNuclearComposition(); - // determine average interaction length - - auto const weightedProdCrossSection = - mediumComposition.getWeightedSum([=](auto vTargetID) { - return std::get<0>(this->getCrossSection(corsikaBeamId, vTargetID, ECoM)); - }); - - CORSIKA_LOG_DEBUG( - "Interaction: IntLength: weighted CrossSection (mb): {} " - "Interaction: IntLength: average mass number: {} ", - weightedProdCrossSection / 1_mb, mediumComposition.getAverageMassNumber()); - - // calculate interaction length in medium - GrammageType const int_length = mediumComposition.getAverageMassNumber() * - constants::u / weightedProdCrossSection; - CORSIKA_LOG_DEBUG("Interaction: interaction length (g/cm2): {} ", - int_length / (0.001_kg) * 1_cm * 1_cm); - - return int_length; + } else { + // we can't test pythia8 internals, LCOV_EXCL_START + throw std::runtime_error("pythia cross section init failed"); + // we can't test pythia8 internals, LCOV_EXCL_STOP } - - return std::numeric_limits<double>::infinity() * 1_g / (1_cm * 1_cm); } template <class TView> - inline void Interaction::doInteraction(TView& view) { + inline void Interaction::doInteraction(TView& view, Code const projectileId, + Code const targetId, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) { auto projectile = view.getProjectile(); - const auto corsikaBeamId = projectile.getPID(); CORSIKA_LOG_DEBUG( "Pythia::Interaction: " - "DoInteraction: {} interaction? ", - corsikaBeamId, corsika::pythia8::Interaction::canInteract(corsikaBeamId)); + "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."); + } + + // position and time of interaction + Point const& pOrig = projectile.getPosition(); + TimeType const tOrig = projectile.getTime(); + + CORSIKA_LOG_DEBUG("Interaction: ebeam lab: {} GeV", eProjectileLab / 1_GeV); - if (is_nucleus(corsikaBeamId)) { - // nuclei handled by different process, this should not happen - throw std::runtime_error("Nuclear projectile are not handled by PYTHIA!"); + // define target kinematics in lab frame + // define boost to and from CoM frame + // CoM frame definition in Pythia projectile: +z + COMBoost const boost(projectileP4, constants::nucleonMass); + auto const& labCS = boost.getOriginalCS(); + + CORSIKA_LOG_DEBUG("Interaction: position of interaction: ", pOrig.getCoordinates()); + CORSIKA_LOG_DEBUG("Interaction: time: {}", tOrig); + + CORSIKA_LOG_DEBUG( + "Interaction: " + " doInteraction: E(GeV): {}" + " Ecm(GeV): {}", + eProjectileLab / 1_GeV, sqrtS / 1_GeV); + + count_++; + + configureLabFrameCollision(projectileId, targetId, eProjectileLab); + + // create event in pytia. LCOV_EXCL_START: we don't validate pythia8 internals + if (!Pythia8::Pythia::next()) + throw std::runtime_error("Pythia::DoInteraction: failed!"); + // LCOV_EXCL_STOP + + // link to pythia stack + Pythia8::Event& event = Pythia8::Pythia::event; + + // LCOV_EXCL_START, we don't validate pythia8 internals + if (print_listing_) { + // print final state + event.list(); } + // LCOV_EXCL_STOP + + MomentumVector Plab_final(labCS, {0.0_GeV, 0.0_GeV, 0.0_GeV}); + HEPEnergyType Elab_final = 0_GeV; + for (int i = 0; i < event.size(); ++i) { + Pythia8::Particle const& p8p = event[i]; + // skip particles that have decayed in pythia + if (!p8p.isFinal()) continue; + + auto const pyId = convert_from_PDG(static_cast<PDGCode>(p8p.id())); - if (corsika::pythia8::Interaction::canInteract(corsikaBeamId)) { - - // define projectile - HEPEnergyType const eProjectileLab = projectile.getEnergy(); - auto const pProjectileLab = projectile.getMomentum(); - CoordinateSystemPtr const& labCS = pProjectileLab.getCoordinateSystem(); - - // position and time of interaction, not used in Sibyll - Point pOrig = projectile.getPosition(); - TimeType tOrig = projectile.getTime(); - - // define target - // FOR NOW: target is always at rest - auto const eTargetLab = 0_GeV + constants::nucleonMass; - auto const pTargetLab = MomentumVector(labCS, 0_GeV, 0_GeV, 0_GeV); - FourVector const PtargLab(eTargetLab, pTargetLab); - - CORSIKA_LOG_DEBUG( - "Interaction: ebeam lab: {} GeV" - "Interaction: pbeam lab: {} GeV", - eProjectileLab / 1_GeV, pProjectileLab.getComponents() / 1_GeV); - - CORSIKA_LOG_DEBUG( - "Interaction: etarget lab: {} GeV" - "Interaction: ptarget lab: {} GeV ", - eTargetLab / 1_GeV, pTargetLab.getComponents() / 1_GeV); - - FourVector const PprojLab(eProjectileLab, pProjectileLab); - - // define target kinematics in lab frame - // define boost to and from CoM frame - // CoM frame definition in Pythia projectile: +z - COMBoost const boost(PprojLab, constants::nucleonMass); - - // just for show: - // boost projecticle - auto const PprojCoM = boost.toCoM(PprojLab); - - // boost target - auto const PtargCoM = boost.toCoM(PtargLab); - - CORSIKA_LOG_DEBUG( - "Interaction: ebeam CoM: {} GeV" - "Interaction: pbeam CoM: {} GeV", - PprojCoM.getTimeLikeComponent() / 1_GeV, - PprojCoM.getSpaceLikeComponents().getComponents() / 1_GeV); - - CORSIKA_LOG_DEBUG( - "Interaction: etarget CoM: {} GeV" - "Interaction: ptarget CoM: {} GeV", - PtargCoM.getTimeLikeComponent() / 1_GeV, - PtargCoM.getSpaceLikeComponents().getComponents() / 1_GeV); - - CORSIKA_LOG_DEBUG("Interaction: position of interaction: ", pOrig.getCoordinates()); - CORSIKA_LOG_DEBUG("Interaction: time: {}", tOrig); - - HEPEnergyType Etot = eProjectileLab + eTargetLab; - MomentumVector Ptot = projectile.getMomentum(); - // invariant mass, i.e. cm. energy - HEPEnergyType Ecm = sqrt(Etot * Etot - Ptot.getSquaredNorm()); - - // sample target mass number - auto const* currentNode = projectile.getNode(); - auto const& mediumComposition = - currentNode->getModelProperties().getNuclearComposition(); - // get cross sections for target materials - /* - Here we read the cross section from the interaction model again, - should be passed from getInteractionLength if possible - */ - //#warning reading interaction cross section again, should not be necessary - auto const& compVec = mediumComposition.getComponents(); - std::vector<si::CrossSectionType> cross_section_of_components(compVec.size()); - - for (size_t i = 0; i < compVec.size(); ++i) { - auto const targetId = compVec[i]; - auto const [sigProd, sigEla] = getCrossSection(corsikaBeamId, targetId, Ecm); - [[maybe_unused]] auto const& dummy_sigEla = sigEla; - cross_section_of_components[i] = sigProd; - } - - auto const corsikaTargetId = - mediumComposition.sampleTarget(cross_section_of_components, RNG_); - CORSIKA_LOG_DEBUG("Interaction: target selected: {}", corsikaTargetId); - - if (corsikaTargetId != Code::Hydrogen && corsikaTargetId != Code::Neutron && - corsikaTargetId != Code::Proton) - throw std::runtime_error("DoInteraction: wrong target for PYTHIA"); - - CORSIKA_LOG_DEBUG( - "Interaction: " - " DoInteraction: E(GeV): {}" - " Ecm(GeV): {}", - eProjectileLab / 1_GeV, Ecm / 1_GeV); - - if (eProjectileLab < 8.5_GeV || !isValidCoMEnergy(Ecm)) { - CORSIKA_LOG_DEBUG( - "Interaction: " - " DoInteraction: should have dropped particle.. " - "THIS IS AN ERROR"); - throw std::runtime_error("energy too low for PYTHIA"); - - } else { - count_++; - - configureLabFrameCollision(corsikaBeamId, corsikaTargetId, eProjectileLab); - - // create event in pytia. LCOV_EXCL_START: we don't validate pythia8 internals - if (!Pythia8::Pythia::next()) - throw std::runtime_error("Pythia::DoInteraction: failed!"); - // LCOV_EXCL_STOP - - // link to pythia stack - Pythia8::Event& event = Pythia8::Pythia::event; - - // LCOV_EXCL_START, we don't validate pythia8 internals - if (print_listing_) { - // print final state - event.list(); - } - // LCOV_EXCL_STOP - - MomentumVector Plab_final(labCS, {0.0_GeV, 0.0_GeV, 0.0_GeV}); - HEPEnergyType Elab_final = 0_GeV; - for (int i = 0; i < event.size(); ++i) { - Pythia8::Particle& p8p = event[i]; - // skip particles that have decayed in pythia - if (!p8p.isFinal()) continue; - - auto const pyId = convert_from_PDG(static_cast<PDGCode>(p8p.id())); - - MomentumVector const pyPlab( - labCS, {p8p.px() * 1_GeV, p8p.py() * 1_GeV, p8p.pz() * 1_GeV}); - - // add to corsika stack - auto pnew = - projectile.addSecondary(std::make_tuple(pyId, pyPlab, pOrig, tOrig)); - - Plab_final += pnew.getMomentum(); - Elab_final += pnew.getEnergy(); - } - CORSIKA_LOG_DEBUG( - "conservation (all GeV): " - "Elab_final= {}" - ", Plab_final= {}", - Elab_final / 1_GeV, (Plab_final / 1_GeV).getComponents()); - } + MomentumVector const pyPlab(labCS, + {p8p.px() * 1_GeV, p8p.py() * 1_GeV, p8p.pz() * 1_GeV}); + + // add to corsika stack + auto pnew = projectile.addSecondary(std::make_tuple(pyId, pyPlab, pOrig, tOrig)); + + Plab_final += pnew.getMomentum(); + Elab_final += pnew.getEnergy(); } + + 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/Interaction.inl b/corsika/detail/modules/qgsjetII/Interaction.inl deleted file mode 100644 index c3147dbb83b00aff24e6f909220ff88586360ed4..0000000000000000000000000000000000000000 --- a/corsika/detail/modules/qgsjetII/Interaction.inl +++ /dev/null @@ -1,397 +0,0 @@ -/* - * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu - * - * This software is distributed under the terms of the GNU General Public - * Licence version 3 (GPL Version 3). See file LICENSE for a full version of - * the license. - */ - -#include <corsika/modules/qgsjetII/Interaction.hpp> - -#include <corsika/media/Environment.hpp> -#include <corsika/media/NuclearComposition.hpp> -#include <corsika/framework/geometry/QuantityVector.hpp> -#include <corsika/framework/geometry/FourVector.hpp> -#include <corsika/modules/qgsjetII/ParticleConversion.hpp> -#include <corsika/modules/qgsjetII/QGSJetIIFragmentsStack.hpp> -#include <corsika/modules/qgsjetII/QGSJetIIStack.hpp> -#include <corsika/framework/utility/COMBoost.hpp> - -#include <sstream> -#include <string> -#include <tuple> - -#include <qgsjet-II-04.hpp> - -namespace corsika::qgsjetII { - - inline Interaction::Interaction(boost::filesystem::path dataPath) { - CORSIKA_LOG_DEBUG("Reading QGSJetII data tables from {}", dataPath); - - // initialize QgsjetII - static bool initialized = false; - if (!initialized) { - qgset_(); - datadir DIR(dataPath.string() + "/"); - qgaini_(DIR.data); - initialized = true; - } - } - - inline Interaction::~Interaction() { - CORSIKA_LOG_DEBUG("QgsjetII::Interaction n= {}", count_); - } - - inline CrossSectionType Interaction::getCrossSection(const Code beamId, - const Code targetId, - const HEPEnergyType Elab, - const unsigned int Abeam, - const unsigned int targetA) const { - double sigProd = std::numeric_limits<double>::infinity(); - - if (corsika::qgsjetII::canInteract(beamId)) { - - int const iBeam = static_cast<QgsjetIIXSClassIntType>( - corsika::qgsjetII::getQgsjetIIXSCode(beamId)); - int iTarget = 1; - if (is_nucleus(targetId)) { - iTarget = targetA; - if (iTarget > int(maxMassNumber_) || iTarget <= 0) { - std::ostringstream txt; - txt << "QgsjetII target outside range. Atarget=" << iTarget; - throw std::runtime_error(txt.str().c_str()); - } - } - int iProjectile = 1; - if (is_nucleus(beamId)) { - iProjectile = Abeam; - if (iProjectile > int(maxMassNumber_) || iProjectile <= 0) { - std::ostringstream txt; - txt << "QgsjetII projectile outside range. Aprojectile=" << iProjectile; - throw std::runtime_error(txt.str().c_str()); - } - } - - CORSIKA_LOG_DEBUG( - "QgsjetII::getCrossSection Elab= {} GeV iBeam= {}" - " iProjectile= {} iTarget= {}", - Elab / 1_GeV, iBeam, iProjectile, iTarget); - sigProd = qgsect_(Elab / 1_GeV, iBeam, iProjectile, iTarget); - CORSIKA_LOG_DEBUG("QgsjetII::getCrossSection sigProd= {} mb", sigProd); - } - - return sigProd * 1_mb; - } - - template <typename TParticle> - inline GrammageType Interaction::getInteractionLength(const TParticle& particle) const { - - // coordinate system, get global frame of reference - CoordinateSystemPtr const& rootCS = get_root_CoordinateSystem(); - - const Code corsikaBeamId = particle.getPID(); - - // beam particles for qgsjetII : 1, 2, 3 for p, pi, k - // read from cross section code table - const bool kInteraction = corsika::qgsjetII::canInteract(corsikaBeamId); - - // FOR NOW: assume target is at rest - MomentumVector pTarget(rootCS, {0_GeV, 0_GeV, 0_GeV}); - - // total momentum and energy - HEPEnergyType const Elab = particle.getEnergy(); - - CORSIKA_LOG_DEBUG( - "Interaction: LambdaInt: \n" - " input energy: {} GeV" - " beam can interact: {}" - " beam pid: {}", - particle.getEnergy() / 1_GeV, kInteraction, corsikaBeamId); - - if (kInteraction) { - - int Abeam = 0; - if (is_nucleus(corsikaBeamId)) Abeam = get_nucleus_A(corsikaBeamId); - - // get target from environment - /* - the target should be defined by the Environment, - ideally as full particle object so that the four momenta - and the boosts can be defined.. - */ - - auto const* currentNode = particle.getNode(); - const auto& mediumComposition = - currentNode->getModelProperties().getNuclearComposition(); - - CrossSectionType weightedProdCrossSection = - mediumComposition.getWeightedSum([=](Code targetID) -> CrossSectionType { - int targetA = 0; - if (is_nucleus(targetID)) targetA = get_nucleus_A(targetID); - return getCrossSection(corsikaBeamId, targetID, Elab, Abeam, targetA); - }); - - CORSIKA_LOG_DEBUG( - "Interaction: " - "IntLength: weighted CrossSection (mb): {}", - weightedProdCrossSection / 1_mb); - - // calculate interaction length in medium - GrammageType const int_length = mediumComposition.getAverageMassNumber() * - constants::u / weightedProdCrossSection; - CORSIKA_LOG_DEBUG( - "Interaction: " - "interaction length (g/cm2): {}", - int_length / (0.001_kg) * 1_cm * 1_cm); - - return int_length; - } - - return std::numeric_limits<double>::infinity() * 1_g / (1_cm * 1_cm); - } - - /** - In this function QGSJETII is called to produce one event. The - event is copied (and boosted) into the shower lab frame. - */ - - template <typename TView> - inline void Interaction::doInteraction(TView& view) { - - auto const projectile = view.getProjectile(); - auto const corsikaBeamId = projectile.getPID(); - CORSIKA_LOG_DEBUG( - "ProcessQgsjetII: " - "doInteraction: {} interaction possible? {}", - corsikaBeamId, corsika::qgsjetII::canInteract(corsikaBeamId)); - - if (!corsika::qgsjetII::canInteract(corsikaBeamId)) return; - - CoordinateSystemPtr const& rootCS = get_root_CoordinateSystem(); - - // position and time of interaction, not used in QgsjetII - Point const pOrig = projectile.getPosition(); - TimeType const tOrig = projectile.getTime(); - - // define target - // for QgsjetII is always a single nucleon - // FOR NOW: target is always at rest - auto const targetEnergyLab = 0_GeV + constants::nucleonMass; - auto const targetMomentumLab = MomentumVector(rootCS, 0_GeV, 0_GeV, 0_GeV); - FourVector const PtargLab(targetEnergyLab, targetMomentumLab); - - // define projectile - HEPEnergyType const projectileEnergyLab = projectile.getEnergy(); - auto const projectileMomentumLab = projectile.getMomentum(); - - int beamA = 0; - if (is_nucleus(corsikaBeamId)) beamA = get_nucleus_A(corsikaBeamId); - - HEPEnergyType const projectileEnergyLabPerNucleon = projectileEnergyLab / beamA; - - CORSIKA_LOG_DEBUG( - "ebeam lab: {} GeV " - "pbeam lab: {} GeV ", - projectileEnergyLab / 1_GeV, projectileMomentumLab.getComponents() / 1_GeV); - CORSIKA_LOG_DEBUG( - "etarget lab: {} GeV " - "ptarget lab: {} GeV ", - targetEnergyLab / 1_GeV, targetMomentumLab.getComponents() / 1_GeV); - CORSIKA_LOG_DEBUG("position of interaction: {}", pOrig.getCoordinates()); - CORSIKA_LOG_DEBUG("time: {} ", tOrig); - - // sample target mass number - auto const* currentNode = projectile.getNode(); - auto const& mediumComposition = - currentNode->getModelProperties().getNuclearComposition(); - // get cross sections for target materials - /* - Here we read the cross section from the interaction model again, - should be passed from getInteractionLength if possible - */ - auto const& compVec = mediumComposition.getComponents(); - std::vector<CrossSectionType> cross_section_of_components(compVec.size()); - - for (size_t i = 0; i < compVec.size(); ++i) { - auto const targetId = compVec[i]; - int targetA = 0; - if (is_nucleus(targetId)) targetA = get_nucleus_A(targetId); - const auto sigProd = - getCrossSection(corsikaBeamId, targetId, projectileEnergyLab, beamA, targetA); - cross_section_of_components[i] = sigProd; - } - - const auto targetCode = - mediumComposition.sampleTarget(cross_section_of_components, rng_); - - int targetMassNumber = 1; // proton - if (is_nucleus(targetCode)) { // nucleus - targetMassNumber = get_nucleus_A(targetCode); - if (targetMassNumber > int(maxMassNumber_)) - throw std::runtime_error( - "QgsjetII target mass outside range."); // LCOV_EXCL_LINE there is no - // allowed path here - } else { - if (targetCode != Proton::code) // LCOV_EXCL_LINE there is no allowed path here - throw std::runtime_error( - "QgsjetII Taget not possible."); // LCOV_EXCL_LINE there is no allowed path - // here - } - CORSIKA_LOG_DEBUG("target: {}, qgsjetII code/A: {}", targetCode, targetMassNumber); - - int projectileMassNumber = 1; // "1" means "hadron" - QgsjetIIHadronType qgsjet_hadron_type = - qgsjetII::getQgsjetIIHadronType(corsikaBeamId); - if (qgsjet_hadron_type == QgsjetIIHadronType::NucleusType) { - projectileMassNumber = get_nucleus_A(corsikaBeamId); - if (projectileMassNumber > int(maxMassNumber_)) - throw std::runtime_error( - "QgsjetII projectile mass outside range."); // LCOV_EXCL_LINE there is no - // allowed path here - std::array<QgsjetIIHadronType, 2> constexpr nucleons = { - QgsjetIIHadronType::ProtonType, QgsjetIIHadronType::NeutronType}; - std::uniform_int_distribution select(0, 1); - qgsjet_hadron_type = nucleons[select(rng_)]; - } else { - // from conex: replace pi0 or rho0 with pi+/pi- in alternating sequence - if (qgsjet_hadron_type == QgsjetIIHadronType::NeutralLightMesonType) { - qgsjet_hadron_type = alternate_; - alternate_ = (alternate_ == QgsjetIIHadronType::PiPlusType - ? QgsjetIIHadronType::PiMinusType - : QgsjetIIHadronType::PiPlusType); - } - } - - // beam id for qgsjetII - int kBeam = 2; // default: proton Shouldn't we randomize neutron/proton for nuclei? - if (!is_nucleus(corsikaBeamId)) { - kBeam = corsika::qgsjetII::convertToQgsjetIIRaw(corsikaBeamId); - // from conex - if (kBeam == 0) { // replace pi0 or rho0 with pi+/pi- - static int select = 1; - kBeam = select; - select *= -1; - } - // replace lambda by neutron - if (kBeam == 6) - kBeam = 3; - else if (kBeam == -6) - kBeam = -3; - // else if (abs(kBeam)>6) -> throw - } - - count_++; - int qgsjet_hadron_type_int = static_cast<QgsjetIICodeIntType>(qgsjet_hadron_type); - CORSIKA_LOG_DEBUG( - "qgsjet_hadron_type_int={} projectileMassNumber={} targetMassNumber={}", - qgsjet_hadron_type_int, projectileMassNumber, targetMassNumber); - qgini_(projectileEnergyLab / 1_GeV, qgsjet_hadron_type_int, projectileMassNumber, - targetMassNumber); - qgconf_(); - - // bookkeeping - MomentumVector Plab_final(rootCS, {0.0_GeV, 0.0_GeV, 0.0_GeV}); - HEPEnergyType Elab_final = 0_GeV; - - // to read the secondaries - // define rotation to and from CoM frame - // CoM frame definition in QgsjetII projectile: +z - auto const& originalCS = projectileMomentumLab.getCoordinateSystem(); - CoordinateSystemPtr const zAxisFrame = - make_rotationToZ(originalCS, projectileMomentumLab); - - // fragments - QGSJetIIFragmentsStack qfs; - for (auto& fragm : qfs) { - int const A = fragm.getFragmentSize(); - if (A == 1) { // nucleon - std::uniform_real_distribution<double> select; - Code idFragm = Code::Proton; - if (select(rng_) > 0.5) { idFragm = Code::Neutron; } - - const HEPMassType nucleonMass = get_mass(idFragm); - // no pT, frgments just go forward - auto momentum = - Vector(zAxisFrame, corsika::QuantityVector<hepmomentum_d>{ - 0.0_GeV, 0.0_GeV, - sqrt((projectileEnergyLabPerNucleon + nucleonMass) * - (projectileEnergyLabPerNucleon - nucleonMass))}); - - momentum.rebase(originalCS); // transform back into standard lab frame - CORSIKA_LOG_DEBUG( - "secondary fragment> id= {}" - " p={}", - idFragm, momentum.getComponents()); - auto pnew = view.addSecondary(std::make_tuple(idFragm, momentum, pOrig, tOrig)); - Plab_final += pnew.getMomentum(); - Elab_final += pnew.getEnergy(); - - } else { // nucleus, A>1 - - int Z = 0; - switch (A) { - case 2: // deuterium - Z = 1; - break; - case 3: // tritium - Z = 1; - break; - case 4: // helium - Z = 2; - break; - default: // nucleus - { - Z = int(A / 2.15 + 0.7); - } - } - - HEPMassType const nucleusMass = Proton::mass * Z + Neutron::mass * (A - Z); - // no pT, frgments just go forward - auto momentum = Vector( - zAxisFrame, QuantityVector<hepmomentum_d>{ - 0.0_GeV, 0.0_GeV, - sqrt((projectileEnergyLabPerNucleon * A + nucleusMass) * - (projectileEnergyLabPerNucleon * A - nucleusMass))}); - - momentum.rebase(originalCS); // transform back into standard lab frame - CORSIKA_LOG_DEBUG( - "secondary fragment> id={}" - " p={}" - " A={}" - " Z={}", - get_nucleus_code(A, Z), momentum.getComponents(), A, Z); - - auto pnew = view.addSecondary( - std::make_tuple(get_nucleus_code(A, Z), momentum, pOrig, tOrig)); - Plab_final += pnew.getMomentum(); - Elab_final += pnew.getEnergy(); - } - } - - // secondaries - QGSJetIIStack qs; - for (auto& psec : qs) { - - auto momentum = psec.getMomentum(zAxisFrame); - - momentum.rebase(originalCS); // transform back into standard lab frame - CORSIKA_LOG_DEBUG("secondary> id= {}, p= {}", - corsika::qgsjetII::convertFromQgsjetII(psec.getPID()), - momentum.getComponents()); - auto pnew = view.addSecondary(std::make_tuple( - corsika::qgsjetII::convertFromQgsjetII(psec.getPID()), momentum, pOrig, tOrig)); - Plab_final += pnew.getMomentum(); - Elab_final += pnew.getEnergy(); - } - CORSIKA_LOG_DEBUG( - "conservation (all GeV): Ecm_final= n/a " /* << Ecm_final / 1_GeV*/ - ", Elab_final={} " - ", Plab_final={}" - ", N_wounded,targ={}" - ", N_wounded,proj={}" - ", N_fragm,proj={}", - Elab_final / 1_GeV, (Plab_final / 1_GeV).getComponents(), - QGSJetIIFragmentsStackData::getWoundedNucleonsTarget(), - QGSJetIIFragmentsStackData::getWoundedNucleonsProjectile(), qfs.getSize()); - } -} // namespace corsika::qgsjetII diff --git a/corsika/detail/modules/qgsjetII/InteractionModel.inl b/corsika/detail/modules/qgsjetII/InteractionModel.inl new file mode 100644 index 0000000000000000000000000000000000000000..8203faa49d1c9c6c3fe16de453f01dcde138e3bb --- /dev/null +++ b/corsika/detail/modules/qgsjetII/InteractionModel.inl @@ -0,0 +1,300 @@ +/* + * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu + * + * This software is distributed under the terms of the GNU General Public + * Licence version 3 (GPL Version 3). See file LICENSE for a full version of + * the license. + */ + +#include <corsika/modules/qgsjetII/InteractionModel.hpp> + +#include <corsika/framework/geometry/FourVector.hpp> +#include <corsika/framework/geometry/Point.hpp> + +#include <corsika/modules/qgsjetII/ParticleConversion.hpp> +#include <corsika/modules/qgsjetII/QGSJetIIFragmentsStack.hpp> +#include <corsika/modules/qgsjetII/QGSJetIIStack.hpp> + +#include <corsika/framework/utility/COMBoost.hpp> + +#include <sstream> +#include <tuple> + +#include <qgsjet-II-04.hpp> + +namespace corsika::qgsjetII { + + inline InteractionModel::InteractionModel(boost::filesystem::path const dataPath) { + CORSIKA_LOG_DEBUG("Reading QGSJetII data tables from {}", dataPath); + + // initialize QgsjetII + static bool initialized = false; + if (!initialized) { + qgset_(); + datadir DIR(dataPath.string() + "/"); + qgaini_(DIR.data); + initialized = true; + } + } + + inline InteractionModel::~InteractionModel() { + CORSIKA_LOG_DEBUG("QgsjetII::InteractionModel n= {}", count_); + } + + inline bool InteractionModel::isValid(Code const projectileId, Code const targetId, + HEPEnergyType const sqrtS) const { + + if (sqrtS < sqrtSmin_) { return false; } + if (is_nucleus(targetId)) { + size_t iTarget = get_nucleus_A(targetId); + if (iTarget > int(maxMassNumber_) || iTarget <= 0) { return false; } + } else if (targetId != Proton::code) { + return false; + } + + if (is_nucleus(projectileId)) { + size_t iProjectile = get_nucleus_A(projectileId); + if (iProjectile > int(maxMassNumber_) || iProjectile <= 0) { return false; } + } else if (!is_hadron(projectileId)) { + return false; + } + return true; + } + + inline CrossSectionType InteractionModel::getCrossSection( + Code const projectileId, Code const targetId, FourMomentum const& projectileP4, + FourMomentum const& targetP4) const { + + if (!corsika::qgsjetII::canInteract(projectileId)) { + return CrossSectionType::zero(); + } + + // define projectile, in lab frame + auto const sqrtS2 = (projectileP4 + targetP4).getNormSqr(); + auto const sqrtS = sqrt(sqrtS2); + if (!isValid(projectileId, targetId, sqrtS)) { return CrossSectionType::zero(); } + HEPEnergyType const Elab = (sqrtS2 - static_pow<2>(get_mass(projectileId)) - + static_pow<2>(get_mass(targetId))) / + (2 * get_mass(targetId)); + + int const iBeam = static_cast<QgsjetIIXSClassIntType>( + corsika::qgsjetII::getQgsjetIIXSCode(projectileId)); + int iTarget = 1; + if (is_nucleus(targetId)) { iTarget = get_nucleus_A(targetId); } + int iProjectile = 1; + if (is_nucleus(projectileId)) { iProjectile = get_nucleus_A(projectileId); } + + CORSIKA_LOG_DEBUG( + "QgsjetII::getCrossSection Elab= {} GeV iBeam= {}" + " iProjectile= {} iTarget= {}", + Elab / 1_GeV, iBeam, iProjectile, iTarget); + double sigProd = qgsect_(Elab / 1_GeV, iBeam, iProjectile, iTarget); + CORSIKA_LOG_DEBUG("QgsjetII::getCrossSection sigProd= {} mb", sigProd); + return sigProd * 1_mb; + } + + template <typename TSecondaries> + inline void InteractionModel::doInteraction(TSecondaries& view, Code const projectileId, + Code const targetId, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) { + + CORSIKA_LOG_DEBUG( + "ProcessQgsjetII: " + "doInteraction: {} interaction possible? {}", + projectileId, corsika::qgsjetII::canInteract(projectileId)); + + // define projectile, in lab frame + auto const sqrtS2 = (projectileP4 + targetP4).getNormSqr(); + auto const sqrtS = sqrt(sqrtS2); + if (!corsika::qgsjetII::canInteract(projectileId) || + !isValid(projectileId, targetId, sqrtS)) { + throw std::runtime_error("invalid target/projectile/energy combination."); + } + HEPEnergyType const Elab = (sqrtS2 - static_pow<2>(get_mass(projectileId)) - + static_pow<2>(get_mass(targetId))) / + (2 * get_mass(targetId)); + + int beamA = 0; + if (is_nucleus(projectileId)) { beamA = get_nucleus_A(projectileId); } + + CORSIKA_LOG_DEBUG("ebeam lab: {} GeV ", Elab / 1_GeV); + + int targetMassNumber = 1; // proton + if (is_nucleus(targetId)) { // nucleus + targetMassNumber = get_nucleus_A(targetId); + } + CORSIKA_LOG_DEBUG("target: {}, qgsjetII code/A: {}", targetId, targetMassNumber); + + // select QGSJetII internal projectile type + int projectileMassNumber = 1; // "1" means "hadron" + QgsjetIIHadronType qgsjet_hadron_type = qgsjetII::getQgsjetIIHadronType(projectileId); + if (qgsjet_hadron_type == QgsjetIIHadronType::NucleusType) { + projectileMassNumber = get_nucleus_A(projectileId); + std::array<QgsjetIIHadronType, 2> constexpr nucleons = { + QgsjetIIHadronType::ProtonType, QgsjetIIHadronType::NeutronType}; + std::uniform_int_distribution select(0, 1); + qgsjet_hadron_type = nucleons[select(rng_)]; + } else if (qgsjet_hadron_type == QgsjetIIHadronType::NeutralLightMesonType) { + // from conex: replace pi0 or rho0 with pi+/pi- in alternating sequence + qgsjet_hadron_type = alternate_; + alternate_ = + (alternate_ == QgsjetIIHadronType::PiPlusType ? QgsjetIIHadronType::PiMinusType + : QgsjetIIHadronType::PiPlusType); + } + + count_++; + int qgsjet_hadron_type_int = static_cast<QgsjetIICodeIntType>(qgsjet_hadron_type); + CORSIKA_LOG_DEBUG( + "qgsjet_hadron_type_int={} projectileMassNumber={} targetMassNumber={}", + qgsjet_hadron_type_int, projectileMassNumber, targetMassNumber); + qgini_(Elab / 1_GeV, qgsjet_hadron_type_int, projectileMassNumber, targetMassNumber); + qgconf_(); + + CoordinateSystemPtr const& rootCS = get_root_CoordinateSystem(); + + // bookkeeping + MomentumVector Plab_final(rootCS, {0.0_GeV, 0.0_GeV, 0.0_GeV}); + HEPEnergyType Elab_final = 0_GeV; + + // to read the secondaries + // define rotation to and from CoM frame + // CoM frame definition in QgsjetII projectile: +z + + // QGSJetII, both, in input and output only considers the lab frame with a target at + // rest. + + // system of initial-state + COMBoost boost(projectileP4, targetP4); + + auto const& originalCS = boost.getOriginalCS(); + auto const& csPrime = + boost.getRotatedCS(); // z is along the CM motion (projectile, in Cascade) + + HEPMomentumType const pLabMag = + sqrt((Elab - get_mass(projectileId)) * (Elab + get_mass(projectileId))); + MomentumVector pLab(csPrime, {0_eV, 0_eV, pLabMag}); + + // internal QGSJetII system + COMBoost boostInternal({Elab, pLab}, get_mass(targetId)); + + // position and time of interaction, not used in QgsjetII + auto const projectile = view.getProjectile(); + Point const pOrig = projectile.getPosition(); + TimeType const tOrig = projectile.getTime(); + + // fragments + QGSJetIIFragmentsStack qfs; + for (auto& fragm : qfs) { + int const A = fragm.getFragmentSize(); + if (A == 1) { // nucleon + std::uniform_real_distribution<double> select; + Code idFragm = Code::Proton; + if (select(rng_) > 0.5) { idFragm = Code::Neutron; } + + const HEPMassType nucleonMass = get_mass(idFragm); + // no pT, fragments just go forward + HEPEnergyType const projectileEnergyLabPerNucleon = Elab / beamA; + MomentumVector momentum{csPrime, + {0.0_GeV, 0.0_GeV, + sqrt((projectileEnergyLabPerNucleon + nucleonMass) * + (projectileEnergyLabPerNucleon - nucleonMass))}}; + + // this is not "CoM" here, but rather the system defined by projectile+target, + // which in Cascade-mode is already lab + auto const P4com = + boostInternal.toCoM(FourVector{projectileEnergyLabPerNucleon, momentum}); + auto const P4output = boost.fromCoM(P4com); + auto p3output = P4output.getSpaceLikeComponents(); + p3output.rebase(originalCS); // transform back into standard lab frame + + CORSIKA_LOG_DEBUG( + "secondary fragment> id= {}" + " p={}", + idFragm, p3output.getComponents()); + auto pnew = view.addSecondary(std::make_tuple(idFragm, p3output, pOrig, tOrig)); + Plab_final += pnew.getMomentum(); + Elab_final += pnew.getEnergy(); + + } else { // nucleus, A>1 + + int Z = 0; + switch (A) { + case 2: // deuterium + Z = 1; + break; + case 3: // tritium + Z = 1; + break; + case 4: // helium + Z = 2; + break; + default: // nucleus + { + Z = int(A / 2.15 + 0.7); + } + } + + HEPMassType const nucleusMass = Proton::mass * Z + Neutron::mass * (A - Z); + // no pT, frgments just go forward + HEPEnergyType const projectileEnergyLabPerNucleon = Elab / beamA; + MomentumVector momentum{ + csPrime, + {0.0_GeV, 0.0_GeV, + sqrt((projectileEnergyLabPerNucleon * A + nucleusMass) * + (projectileEnergyLabPerNucleon * A - nucleusMass))}}; + + // this is not "CoM" here, but rather the system defined by projectile+target, + // which in Cascade-mode is already lab + auto const P4com = + boostInternal.toCoM(FourVector{projectileEnergyLabPerNucleon * A, momentum}); + auto const P4output = boost.fromCoM(P4com); + auto p3output = P4output.getSpaceLikeComponents(); + p3output.rebase(originalCS); // transform back into standard lab frame + + CORSIKA_LOG_DEBUG( + "secondary fragment> id={}" + " p={}" + " A={}" + " Z={}", + get_nucleus_code(A, Z), p3output.getComponents(), A, Z); + + auto pnew = view.addSecondary( + std::make_tuple(get_nucleus_code(A, Z), p3output, pOrig, tOrig)); + Plab_final += pnew.getMomentum(); + Elab_final += pnew.getEnergy(); + } + } + + // secondaries + QGSJetIIStack qs; + for (auto& psec : qs) { + + auto momentum = psec.getMomentum(csPrime); + // this is not "CoM" here, but rather the system defined by projectile+target, + // which in Cascade-mode is already lab + auto const P4com = boostInternal.toCoM(FourVector{psec.getEnergy(), momentum}); + auto const P4output = boost.fromCoM(P4com); + auto p3output = P4output.getSpaceLikeComponents(); + p3output.rebase(originalCS); // transform back into standard lab frame + + CORSIKA_LOG_DEBUG("secondary> id= {}, p= {}", + corsika::qgsjetII::convertFromQgsjetII(psec.getPID()), + p3output.getComponents()); + auto pnew = view.addSecondary(std::make_tuple( + corsika::qgsjetII::convertFromQgsjetII(psec.getPID()), p3output, pOrig, tOrig)); + Plab_final += pnew.getMomentum(); + Elab_final += pnew.getEnergy(); + } + CORSIKA_LOG_DEBUG( + "conservation (all GeV): Ecm_final= n/a " /* << Ecm_final / 1_GeV*/ + ", Elab_final={} " + ", Plab_final={}" + ", N_wounded,targ={}" + ", N_wounded,proj={}" + ", N_fragm,proj={}", + Elab_final / 1_GeV, (Plab_final / 1_GeV).getComponents(), + QGSJetIIFragmentsStackData::getWoundedNucleonsTarget(), + QGSJetIIFragmentsStackData::getWoundedNucleonsProjectile(), qfs.getSize()); + } +} // namespace corsika::qgsjetII diff --git a/corsika/detail/modules/sibyll/Decay.inl b/corsika/detail/modules/sibyll/Decay.inl index 6b8ae281d4a4be07408785e1a846c376c8b4bc0f..ab0114182b41f5db321f19ec6d91458cc9b5000d 100644 --- a/corsika/detail/modules/sibyll/Decay.inl +++ b/corsika/detail/modules/sibyll/Decay.inl @@ -174,7 +174,7 @@ namespace corsika::sibyll { count_++; // remember position - Point const decayPoint = projectile.getPosition(); + Point const& decayPoint = projectile.getPosition(); TimeType const t0 = projectile.getTime(); // switch on decay for this particle setUnstable(pCode); diff --git a/corsika/detail/modules/sibyll/Interaction.inl b/corsika/detail/modules/sibyll/Interaction.inl deleted file mode 100644 index 07f390b7385a6be73b155b8fe8dd8a64b4308c7a..0000000000000000000000000000000000000000 --- a/corsika/detail/modules/sibyll/Interaction.inl +++ /dev/null @@ -1,348 +0,0 @@ -/* - * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu - * - * This software is distributed under the terms of the GNU General Public - * Licence version 3 (GPL Version 3). See file LICENSE for a full version of - * the license. - */ - -#pragma once - -#include <corsika/modules/sibyll/Interaction.hpp> - -#include <corsika/media/Environment.hpp> -#include <corsika/media/NuclearComposition.hpp> -#include <corsika/framework/geometry/FourVector.hpp> -#include <corsika/modules/sibyll/ParticleConversion.hpp> -#include <corsika/modules/sibyll/SibStack.hpp> -#include <corsika/framework/utility/COMBoost.hpp> - -#include <sibyll2.3d.hpp> - -#include <tuple> - -namespace corsika::sibyll { - - inline Interaction::Interaction(const bool sibyll_printout_on) - : sibyll_listing_(sibyll_printout_on) { - // initialize Sibyll - static bool initialized = false; - if (!initialized) { - sibyll_ini_(); - initialized = true; - } - } - - inline Interaction::~Interaction() { - CORSIKA_LOG_DEBUG("Sibyll::Interaction n={}, Nnuc={}", count_, nucCount_); - } - - inline std::tuple<corsika::CrossSectionType, corsika::CrossSectionType> - Interaction::getCrossSection(const corsika::Code BeamId, const corsika::Code TargetId, - const corsika::HEPEnergyType CoMenergy) const { - double sigProd, sigEla, dummy, dum1, dum3, dum4; - double dumdif[3]; - const int iBeam = corsika::sibyll::getSibyllXSCode( - BeamId); // 0 (can not interact, 1: proton-like, 2: pion-like, 3:kaon-like) - if (!iBeam) - throw std::runtime_error( - fmt::format("Interaction of beam {} not defined in " - "Sibyll!", - BeamId)); - if (!isValidCoMEnergy(CoMenergy)) { - throw std::runtime_error( - "Interaction: getCrossSection: CoM energy outside range for Sibyll!"); - } - const double dEcm = CoMenergy / 1_GeV; - // single nucleon target (p,n, hydrogen) or 4<=A<=18 - if (isValidTarget(TargetId)) { - // single nucleon target - if (TargetId == corsika::Code::Proton || TargetId == Code::Hydrogen || - TargetId == Code::Neutron) { - sib_sigma_hp_(iBeam, dEcm, dum1, sigEla, sigProd, dumdif, dum3, dum4); - } else { - // nuclear target - const int iTarget = corsika::get_nucleus_A(TargetId); - sib_sigma_hnuc_(iBeam, iTarget, dEcm, sigProd, dummy, sigEla); - } - } else { - // throw std::runtime_error( - // "Sibyll nuclear target outside range. Only nuclei with 4<=A<18 are - // allowed."); - - // no interaction in sibyll possible, return infinite cross section? or throw? - sigProd = std::numeric_limits<double>::infinity(); - sigEla = std::numeric_limits<double>::infinity(); - } - return std::make_tuple(sigProd * 1_mb, sigEla * 1_mb); - } - - template <typename TParticle> - inline corsika::GrammageType Interaction::getInteractionLength( - TParticle const& projectile) const { - - const corsika::Code corsikaBeamId = projectile.getPID(); - - // beam corsika for sibyll : 1, 2, 3 for p, pi, k - // read from cross section code table - const bool kInteraction = corsika::sibyll::canInteract(corsikaBeamId); - - MomentumVector const& pLab = projectile.getMomentum(); - CoordinateSystemPtr const& labCS = pLab.getCoordinateSystem(); - - // FOR NOW: assume target is at rest - MomentumVector pTarget(labCS, {0_GeV, 0_GeV, 0_GeV}); - - // total momentum and energy - HEPEnergyType Elab = projectile.getEnergy() + constants::nucleonMass; - MomentumVector pTotLab(labCS, {0_GeV, 0_GeV, 0_GeV}); - pTotLab += pLab; - pTotLab += pTarget; - auto const pTotLabNorm = pTotLab.getNorm(); - // calculate cm. energy - const HEPEnergyType ECoM = sqrt( - (Elab + pTotLabNorm) * (Elab - pTotLabNorm)); // binomial for numerical accuracy - - CORSIKA_LOG_DEBUG( - "Interaction: LambdaInt: \n" - " input energy: {} GeV " - " beam can interact: {} " - " beam pid: {}", - projectile.getEnergy() / 1_GeV, kInteraction, projectile.getPID()); - - // TODO: move limits into variables - // FR: removed && Elab >= 8.5_GeV - if (kInteraction && isValidCoMEnergy(ECoM)) { - - // get target from environment - /* - the target should be defined by the Environment, - ideally as full particle object so that the four momenta - and the boosts can be defined.. - */ - - auto const* currentNode = projectile.getNode(); - const auto& mediumComposition = - currentNode->getModelProperties().getNuclearComposition(); - - si::CrossSectionType weightedProdCrossSection = mediumComposition.getWeightedSum( - [=](corsika::Code targetID) -> si::CrossSectionType { - // Argon needs special handling .... - return targetID == Code::Argon ? CrossSectionType::zero() - : std::get<0>(this->getCrossSection( - corsikaBeamId, targetID, ECoM)); - }); - - CORSIKA_LOG_DEBUG( - "Interaction: " - "IntLength: weighted CrossSection (mb): {} ", - weightedProdCrossSection / 1_mb); - - // calculate interaction length in medium - GrammageType const int_length = mediumComposition.getAverageMassNumber() * - constants::u / weightedProdCrossSection; - CORSIKA_LOG_DEBUG( - "Interaction: " - "interaction length (g/cm2): {} ", - int_length / (0.001_kg) * 1_cm * 1_cm); - - return int_length; - } - - return std::numeric_limits<double>::infinity() * 1_g / (1_cm * 1_cm); - } - - /** - In this function SIBYLL is called to produce one event. The - event is copied (and boosted) into the shower lab frame. - */ - - template <typename TSecondaryView> - inline void Interaction::doInteraction(TSecondaryView& view) { - - auto const projectile = view.getProjectile(); - const auto corsikaBeamId = projectile.getPID(); - - if (corsika::is_nucleus(corsikaBeamId)) { - // nuclei handled by different process, this should not happen - throw std::runtime_error("Nuclear projectile are not handled by SIBYLL!"); - } - - // position and time of interaction, not used in Sibyll - Point const pOrig = projectile.getPosition(); - TimeType const tOrig = projectile.getTime(); - - // define projectile - HEPEnergyType const eProjectileLab = projectile.getEnergy(); - auto const pProjectileLab = projectile.getMomentum(); - CoordinateSystemPtr const& originalCS = pProjectileLab.getCoordinateSystem(); - - CORSIKA_LOG_DEBUG( - "ProcessSibyll: " - "DoInteraction: pid {} interaction ", - corsikaBeamId); - - // define target - // for Sibyll is always a single nucleon - // FOR NOW: target is always at rest - const auto eTargetLab = 0_GeV + constants::nucleonMass; - const auto pTargetLab = MomentumVector(originalCS, 0_GeV, 0_GeV, 0_GeV); - const FourVector PtargLab(eTargetLab, pTargetLab); - - CORSIKA_LOG_DEBUG( - "Interaction: ebeam lab: {} GeV" - "Interaction: pbeam lab: {} GeV", - eProjectileLab / 1_GeV, pProjectileLab.getComponents()); - CORSIKA_LOG_DEBUG( - "Interaction: etarget lab: {} GeV " - "Interaction: ptarget lab: {} GeV", - eTargetLab / 1_GeV, pTargetLab.getComponents() / 1_GeV); - - const FourVector PprojLab(eProjectileLab, pProjectileLab); - - // define target kinematics in lab frame - // define boost to and from CoM frame - // CoM frame definition in Sibyll projectile: +z - COMBoost const boost(PprojLab, constants::nucleonMass); - auto const& csPrime = boost.getRotatedCS(); - - // just for show: - // boost projecticle - [[maybe_unused]] auto const PprojCoM = boost.toCoM(PprojLab); - // boost target - [[maybe_unused]] auto const PtargCoM = boost.toCoM(PtargLab); - CORSIKA_LOG_DEBUG( - "Interaction: ebeam CoM: {} GeV " - "Interaction: pbeam CoM: {} GeV ", - PprojCoM.getTimeLikeComponent() / 1_GeV, - PprojCoM.getSpaceLikeComponents().getComponents(csPrime) / 1_GeV); - CORSIKA_LOG_DEBUG( - "Interaction: etarget CoM: {} GeV " - "Interaction: ptarget CoM: {} GeV ", - PtargCoM.getTimeLikeComponent() / 1_GeV, - PtargCoM.getSpaceLikeComponents().getComponents(csPrime) / 1_GeV); - - CORSIKA_LOG_DEBUG("Interaction: position of interaction: {} ", - pOrig.getCoordinates()); - CORSIKA_LOG_DEBUG("Interaction: time: {} ", tOrig); - - HEPEnergyType Etot = eProjectileLab + eTargetLab; - MomentumVector Ptot = projectile.getMomentum(); - // invariant mass, i.e. cm. energy - HEPEnergyType Ecm = sqrt(Etot * Etot - Ptot.getSquaredNorm()); - - // sample target mass number - auto const* currentNode = projectile.getNode(); - auto const& mediumComposition = - currentNode->getModelProperties().getNuclearComposition(); - // get cross sections for target materials - /* - Here we read the cross section from the interaction model again, - should be passed from getInteractionLength if possible - */ - //#warning reading interaction cross section again, should not be necessary - auto const& compVec = mediumComposition.getComponents(); - std::vector<CrossSectionType> cross_section_of_components(compVec.size()); - - for (size_t i = 0; i < compVec.size(); ++i) { - auto const targetId = compVec[i]; - if (targetId == Code::Argon) continue; // skip Argon .... - const auto [sigProd, sigEla] = getCrossSection(corsikaBeamId, targetId, Ecm); - [[maybe_unused]] const auto& dummy_sigEla = sigEla; - cross_section_of_components[i] = sigProd; - } - - const auto targetCode = - mediumComposition.sampleTarget(cross_section_of_components, RNG_); - CORSIKA_LOG_DEBUG("Interaction: target selected: {} ", targetCode); - /* - FOR NOW: allow nuclei with A<18 or protons only. - when medium composition becomes more complex, approximations will have to be - allowed air in atmosphere also contains some Argon. - */ - int targetSibCode = -1; - if (is_nucleus(targetCode)) targetSibCode = get_nucleus_A(targetCode); - if (targetCode == Proton::code) targetSibCode = 1; - CORSIKA_LOG_DEBUG("Interaction: sibyll code: {}", targetSibCode); - if (targetSibCode > int(maxTargetMassNumber_) || targetSibCode < 1) - throw std::runtime_error( - "Sibyll target outside range. Only nuclei with A<18 or protons are " - "allowed."); - - // beam id for sibyll - const int kBeam = corsika::sibyll::convertToSibyllRaw(corsikaBeamId); - - CORSIKA_LOG_DEBUG( - "Interaction: " - " DoInteraction: E(GeV): {} " - " Ecm(GeV): {} ", - eProjectileLab / 1_GeV, Ecm / 1_GeV); - if (Ecm > getMaxEnergyCoM()) - throw std::runtime_error("Interaction::DoInteraction: CoM energy too high!"); - // FR: removed eProjectileLab < 8.5_GeV || - if (Ecm < getMinEnergyCoM()) { - CORSIKA_LOG_DEBUG( - "Interaction: " - " DoInteraction: should have dropped particle.. " - "THIS IS AN ERROR"); - throw std::runtime_error("energy too low for SIBYLL"); - } else { - count_++; - // Sibyll does not know about units.. - const double sqs = Ecm / 1_GeV; - // running sibyll, filling stack - sibyll_(kBeam, targetSibCode, sqs); - - if (sibyll_listing_) { - // print final state - int print_unit = 6; - sib_list_(print_unit); - nucCount_ += get_nwounded() - 1; - } - - // add particles from sibyll to stack - // link to sibyll stack - SibStack ss; - - MomentumVector Plab_final(originalCS, {0.0_GeV, 0.0_GeV, 0.0_GeV}); - HEPEnergyType Elab_final = 0_GeV, Ecm_final = 0_GeV; - for (auto& psib : ss) { - - // abort on particles that have decayed in Sibyll. Should not happen! - if (psib.hasDecayed()) - throw std::runtime_error("found particle that decayed in SIBYLL!"); - - // transform 4-momentum to lab. frame - // note that the momentum needs to be rotated back - auto const tmp = psib.getMomentum().getComponents(); - auto const pCoM = Vector<hepmomentum_d>(csPrime, tmp); - HEPEnergyType const eCoM = psib.getEnergy(); - auto const Plab = boost.fromCoM(FourVector(eCoM, pCoM)); - auto const p3lab = Plab.getSpaceLikeComponents(); - assert(p3lab.getCoordinateSystem() == originalCS); // just to be sure! - - // add to corsika stack - auto pnew = view.addSecondary(std::make_tuple( - corsika::sibyll::convertFromSibyll(psib.getPID()), p3lab, pOrig, tOrig)); - - Plab_final += pnew.getMomentum(); - Elab_final += pnew.getEnergy(); - Ecm_final += psib.getEnergy(); - } - CORSIKA_LOG_DEBUG( - "conservation (all GeV): " - "Ecm_initial(per nucleon)={:.2f}, Ecm_final(per nucleon)={:.2f}, " - "Elab_initial={:.2f}, Elab_final={:.2f}, " - "Elab-diff (%)={:.2f}, " - "m in target nucleons={:.2f}, " - "Plab_initial={:.2f}, " - "Plab_final={:.2f} ", - Ecm / 1_GeV, Ecm_final * 2. / (get_nwounded() + 1) / 1_GeV, Etot / 1_GeV, - Elab_final / 1_GeV, - (Elab_final / (Etot + get_nwounded() * constants::nucleonMass) - 1) * 100, - constants::nucleonMass * get_nwounded() / 1_GeV, - (pProjectileLab / 1_GeV).getComponents(), (Plab_final / 1_GeV).getComponents()); - } - } - -} // namespace corsika::sibyll diff --git a/corsika/detail/modules/sibyll/InteractionModel.inl b/corsika/detail/modules/sibyll/InteractionModel.inl new file mode 100644 index 0000000000000000000000000000000000000000..99f078673c2d80df3c6251bd6d22eed25a077c4f --- /dev/null +++ b/corsika/detail/modules/sibyll/InteractionModel.inl @@ -0,0 +1,190 @@ +/* + * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu + * + * This software is distributed under the terms of the GNU General Public + * Licence version 3 (GPL Version 3). See file LICENSE for a full version of + * the license. + */ + +#pragma once + +#include <corsika/framework/geometry/Point.hpp> + +#include <corsika/modules/sibyll/ParticleConversion.hpp> +#include <corsika/framework/utility/COMBoost.hpp> +#include <corsika/modules/sibyll/SibStack.hpp> + +#include <sibyll2.3d.hpp> + +#include <tuple> + +namespace corsika::sibyll { + + inline void InteractionModel::setVerbose(bool const flag) { sibyll_listing_ = flag; } + + inline InteractionModel::InteractionModel() + : sibyll_listing_(false) { + // initialize Sibyll + static bool initialized = false; + if (!initialized) { + sibyll_ini_(); + initialized = true; + } + } + + inline InteractionModel::~InteractionModel() { + CORSIKA_LOG_DEBUG("Sibyll::Model n={}, Nnuc={}", count_, nucCount_); + } + + inline bool constexpr InteractionModel::isValid(Code const projectileId, + Code const targetId, + HEPEnergyType const sqrtSnn) const { + if ((minEnergyCoM_ > sqrtSnn) || (sqrtSnn > maxEnergyCoM_)) { return false; } + + if (is_nucleus(targetId)) { + size_t const targA = get_nucleus_A(targetId); + if (targA != 1 && (targA < minNuclearTargetA_ || targA >= maxTargetMassNumber_)) { + return false; + } + } else if (targetId != Code::Proton && targetId != Code::Neutron && + targetId != Code::Hydrogen) { + return false; + } + if (is_nucleus(projectileId) || !corsika::sibyll::canInteract(projectileId)) { + return false; + } + return true; + } + + inline std::tuple<CrossSectionType, CrossSectionType> + InteractionModel::getCrossSectionInelEla(Code const projectileId, Code const targetId, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) const { + + int targetSibCode = 1; // nucleon or particle count + if (is_nucleus(targetId)) { targetSibCode = get_nucleus_A(targetId); } + // sqrtS per target nucleon + HEPEnergyType const sqrtSnn = (projectileP4 + targetP4 / targetSibCode).getNorm(); + + if (!isValid(projectileId, targetId, sqrtSnn)) { + return {CrossSectionType::zero(), CrossSectionType::zero()}; + } + + double dummy, dum1, dum3, dum4, dumdif[3]; // dummies needed for fortran call + int const iBeam = corsika::sibyll::getSibyllXSCode( + projectileId); // 0 (can not interact, 1: proton-like, 2: pion-like, + // 3:kaon-like) + + double const dEcm = sqrtSnn / 1_GeV; + // single nucleon target (p,n, hydrogen) or 4<=A<=18 + double sigProd = 0; + double sigEla = 0; + if (targetId == Code::Proton || targetId == Code::Hydrogen || + targetId == Code::Neutron) { + // single nucleon target + sib_sigma_hp_(iBeam, dEcm, dum1, sigEla, sigProd, dumdif, dum3, dum4); + } else { + // nuclear target + int const iTarget = get_nucleus_A(targetId); + sib_sigma_hnuc_(iBeam, iTarget, dEcm, sigProd, dummy, sigEla); + } + return {sigProd * 1_mb, sigEla * 1_mb}; + } // namespace corsika::sibyll + + /** + * In this function SIBYLL is called to produce one event. The + * event is copied (and boosted) into the shower lab frame. + */ + + template <typename TSecondaryView> + inline void InteractionModel::doInteraction(TSecondaryView& secondaries, + Code const projectileId, + Code const targetId, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) { + + int targetSibCode = 1; // nucleon or particle count + if (is_nucleus(targetId)) { targetSibCode = get_nucleus_A(targetId); } + CORSIKA_LOG_DEBUG("sibyll code: {} (nucleon/particle count)", targetSibCode); + + // sqrtS per target nucleon + HEPEnergyType const sqrtSnn = (projectileP4 + targetP4 / targetSibCode).getNorm(); + COMBoost const boost(projectileP4, targetP4 / targetSibCode); + + if (!isValid(projectileId, targetId, sqrtSnn)) { + throw std::runtime_error("Invalid target/projectile/energy combination"); + } + + CORSIKA_LOG_DEBUG("pId={} tId={} sqrtSnn={}GeV", projectileId, targetId, sqrtSnn); + + // beam id for sibyll + int const projectileSibyllCode = corsika::sibyll::convertToSibyllRaw(projectileId); + + count_++; + // Sibyll does not know about units.. + double const sqs = sqrtSnn / 1_GeV; + // running sibyll, filling stack + sibyll_(projectileSibyllCode, targetSibCode, sqs); + + if (sibyll_listing_) { + // print final state + int print_unit = 6; + sib_list_(print_unit); + nucCount_ += get_nwounded() - 1; + } + + // ------ output and particle readout ----- + auto const& csPrime = boost.getRotatedCS(); + + // add particles from sibyll to stack + + // position and time of interaction, not used in Sibyll + auto const& projectile = secondaries.parent(); + Point const& pOrig = projectile.getPosition(); + TimeType const tOrig = projectile.getTime(); // no time in sibyll + + // link to sibyll stack + SibStack ss; + + auto const& originalCS = boost.getOriginalCS(); + MomentumVector Plab_final(originalCS, {0.0_GeV, 0.0_GeV, 0.0_GeV}); + HEPEnergyType Elab_final = 0_GeV, Ecm_final = 0_GeV; + for (auto& psib : ss) { + // abort on particles that have decayed in Sibyll. Should not happen! + if (psib.hasDecayed()) { // LCOV_EXCL_START + throw std::runtime_error("found particle that decayed in SIBYLL!"); + } // LCOV_EXCL_STOP + + // transform 4-momentum to lab. frame + // note that the momentum needs to be rotated back + auto const tmp = psib.getMomentum().getComponents(); + auto const pCoM = MomentumVector(csPrime, tmp); + HEPEnergyType const eCoM = psib.getEnergy(); + auto const P4lab = boost.fromCoM(FourVector{eCoM, pCoM}); + auto const p3lab = P4lab.getSpaceLikeComponents(); + + // add to corsika stack + auto pnew = secondaries.addSecondary(std::make_tuple( + corsika::sibyll::convertFromSibyll(psib.getPID()), p3lab, pOrig, tOrig)); + + Plab_final += pnew.getMomentum(); + Elab_final += pnew.getEnergy(); + Ecm_final += psib.getEnergy(); + } + { // just output + HEPEnergyType const Elab_initial = + static_pow<2>(sqrtSnn) / (2 * constants::nucleonMass); + CORSIKA_LOG_DEBUG( + "conservation (all GeV): " + "sqrtSnn={}, sqrtSnn_final={}, " + "Elab_initial={}, Elab_final={}, " + "diff(%)={}, " + "E in nucleons={}, " + "Plab_final={} ", + sqrtSnn / 1_GeV, Ecm_final * 2. / (get_nwounded() + 1) / 1_GeV, Elab_initial, + Elab_final / 1_GeV, (Elab_final - Elab_initial) / Elab_initial * 100, + constants::nucleonMass * get_nwounded() / 1_GeV, + (Plab_final / 1_GeV).getComponents()); + } + } +} // namespace corsika::sibyll diff --git a/corsika/detail/modules/sibyll/NuclearInteraction.inl b/corsika/detail/modules/sibyll/NuclearInteraction.inl deleted file mode 100644 index 1460b1133616c1347cdd2eaa156bbcd06ac307b1..0000000000000000000000000000000000000000 --- a/corsika/detail/modules/sibyll/NuclearInteraction.inl +++ /dev/null @@ -1,585 +0,0 @@ -/* - * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu - * - * This software is distributed under the terms of the GNU General Public - * Licence version 3 (GPL Version 3). See file LICENSE for a full version of - * the license. - */ - -#pragma once - -#include <corsika/modules/sibyll/Interaction.hpp> -#include <corsika/modules/sibyll/NuclearInteraction.hpp> - -#include <corsika/media/Environment.hpp> -#include <corsika/media/NuclearComposition.hpp> -#include <corsika/framework/geometry/FourVector.hpp> -#include <corsika/framework/core/PhysicalUnits.hpp> -#include <corsika/framework/utility/COMBoost.hpp> -#include <corsika/framework/core/Logging.hpp> - -#include <nuclib.hpp> - -namespace corsika::sibyll { - - template <typename TEnvironment> - inline NuclearInteraction<TEnvironment>::NuclearInteraction(sibyll::Interaction& hadint, - TEnvironment const& env) - : environment_(env) - , hadronicInteraction_(hadint) { - - // initialize hadronic interaction module - - // check compatibility of energy ranges, someone could try to use low-energy model.. - if (!hadronicInteraction_.isValidCoMEnergy(getMinEnergyPerNucleonCoM()) || - !hadronicInteraction_.isValidCoMEnergy(getMaxEnergyPerNucleonCoM())) - throw std::runtime_error( - "NuclearInteraction: hadronic interaction model incompatible!"); - - // initialize nuclib - // TODO: make sure this does not overlap with sibyll - nuc_nuc_ini_(); - - // initialize cross sections - initializeNuclearCrossSections(); - } - - template <typename TEnvironment> - inline NuclearInteraction<TEnvironment>::~NuclearInteraction() { - CORSIKA_LOG_DEBUG("Nuclib::NuclearInteraction n={} Nnuc={}", count_, nucCount_); - } - - template <typename TEnvironment> - inline void NuclearInteraction<TEnvironment>::printCrossSectionTable(Code pCode) { - if (pCode == Code::Argon) { - CORSIKA_LOG_WARN("SIBYLL cannot handle Argon as target!"); - return; - } - const int k = targetComponentsIndex_.at(pCode); - Code pNuclei[] = {Code::Helium, Code::Lithium7, Code::Oxygen, - Code::Neon, Code::Argon, Code::Iron}; - - std::ostringstream table; - table << "Nuclear CrossSectionTable pCode=" << pCode << " :\n en/A "; - for (auto& j : pNuclei) table << std::setw(9) << j; - table << "\n"; - - // loop over energy bins - for (unsigned int i = 0; i < getNEnergyBins(); ++i) { - table << " " << i << " "; - - for (auto& n : pNuclei) { - auto const j = get_nucleus_A(n); - table << " " << std::setprecision(5) << std::setw(8) - << cnucsignuc_.sigma[j - 1][k][i]; - } - table << "\n"; - } - CORSIKA_LOG_DEBUG(table.str()); - } - - template <typename TEnvironment> - inline void NuclearInteraction<TEnvironment>::initializeNuclearCrossSections() { - - auto& universe = *(environment_.getUniverse()); - - auto const allElementsInUniverse = std::invoke([&]() { - std::set<Code> allElementsInUniverse; - auto collectElements = [&](auto& vtn) { - if (vtn.hasModelProperties()) { - auto const& comp = - vtn.getModelProperties().getNuclearComposition().getComponents(); - for (auto const c : comp) allElementsInUniverse.insert(c); - } - }; - universe.walk(collectElements); - return allElementsInUniverse; - }); - - CORSIKA_LOG_DEBUG("NuclearInteraction: initializing nuclear cross sections..."); - - // loop over target components, at most 4!! - int k = -1; - for (auto& ptarg : allElementsInUniverse) { - if (ptarg == Code::Argon) continue; // NEED TO IGNORE Argon .... - ++k; - CORSIKA_LOG_DEBUG("NuclearInteraction: init target component: {}", ptarg); - const int ib = get_nucleus_A(ptarg); - if (!hadronicInteraction_.isValidTarget(ptarg)) { - CORSIKA_LOG_DEBUG( - "NuclearInteraction::InitializeNuclearCrossSections: target nucleus? id={}", - ptarg); - throw std::runtime_error( - " target can not be handled by hadronic interaction model! "); - } - targetComponentsIndex_.insert(std::pair<Code, int>(ptarg, k)); - // loop over energies, fNEnBins log. energy bins - for (unsigned int i = 0; i < getNEnergyBins(); ++i) { - // hard coded energy grid, has to be aligned to definition in signuc2!!, no - // comment.. - const HEPEnergyType Ecm = pow(10., 1. + 1. * i) * 1_GeV; - // get p-p cross sections - auto const protonId = Code::Proton; - auto const [siginel, sigela] = - hadronicInteraction_.getCrossSection(protonId, protonId, Ecm); - const double dsig = siginel / 1_mb; - const double dsigela = sigela / 1_mb; - // loop over projectiles, mass numbers from 2 to fMaxNucleusAProjectile - for (unsigned int j = 1; j < gMaxNucleusAProjectile_; ++j) { - const int jj = j + 1; - double sig_out, dsig_out, sigqe_out, dsigqe_out; - sigma_mc_(jj, ib, dsig, dsigela, gNSample_, sig_out, dsig_out, sigqe_out, - dsigqe_out); - // write to table - cnucsignuc_.sigma[j][k][i] = sig_out; - cnucsignuc_.sigqe[j][k][i] = sigqe_out; - } - } - } - CORSIKA_LOG_DEBUG( - "NuclearInteraction: cross sections for {} " - " components initialized!", - targetComponentsIndex_.size()); - for (auto& ptarg : allElementsInUniverse) { printCrossSectionTable(ptarg); } - } - - template <typename TEnvironment> - inline CrossSectionType NuclearInteraction<TEnvironment>::readCrossSectionTable( - const int ia, Code pTarget, HEPEnergyType elabnuc) { - - const int ib = targetComponentsIndex_.at(pTarget) + 1; // table index in fortran - auto const ECoMNuc = sqrt(2. * constants::nucleonMass * elabnuc); - if (ECoMNuc < getMinEnergyPerNucleonCoM() || ECoMNuc > getMaxEnergyPerNucleonCoM()) - throw std::runtime_error("NuclearInteraction: energy outside tabulated range!"); - const double e0 = elabnuc / 1_GeV; - double sig; - CORSIKA_LOG_DEBUG("ReadCrossSectionTable: {} {} {}", ia, ib, e0); - signuc2_(ia, ib, e0, sig); - CORSIKA_LOG_DEBUG("ReadCrossSectionTable: sig={}", sig); - return sig * 1_mb; - } - - // TODO: remove elastic cross section? - template <typename TEnvironment> - template <typename TParticle> - std::tuple<CrossSectionType, CrossSectionType> inline NuclearInteraction< - TEnvironment>::getCrossSection(TParticle const& projectile, Code const TargetId) { - - if (!is_nucleus(projectile.getPID())) { - throw std::runtime_error( - "NuclearInteraction: getCrossSection: particle not a nucleus!"); - } - - unsigned int const iBeamA = get_nucleus_A(projectile.getPID()); - HEPEnergyType LabEnergyPerNuc = projectile.getEnergy() / iBeamA; - CORSIKA_LOG_DEBUG( - "NuclearInteraction: getCrossSection: called with: beamNuclA={} " - " TargetId={} LabEnergyPerNuc={}GeV ", - iBeamA, TargetId, LabEnergyPerNuc / 1_GeV); - - // use nuclib to calc. nuclear cross sections - // TODO: for now assumes air with hard coded composition - // extend to arbitrary mixtures, requires smarter initialization - // get nuclib projectile code: nucleon number - if (iBeamA > getMaxNucleusAProjectile() || iBeamA < 2) { - CORSIKA_LOG_DEBUG( - "NuclearInteraction: beam nucleus outside allowed range for NUCLIB!" - "A=" + - std::to_string(iBeamA)); - throw std::runtime_error( - "NuclearInteraction: getCrossSection: beam nucleus outside allowed range for " - "NUCLIB!"); - } - - if (hadronicInteraction_.isValidTarget(TargetId)) { - auto const sigProd = readCrossSectionTable(iBeamA, TargetId, LabEnergyPerNuc); - CORSIKA_LOG_DEBUG("cross section (mb): " + std::to_string(sigProd / 1_mb)); - return std::make_tuple(sigProd, 0_mb); - } else { - throw std::runtime_error("target outside range."); - } - return std::make_tuple(std::numeric_limits<double>::infinity() * 1_mb, - std::numeric_limits<double>::infinity() * 1_mb); - } - - template <typename TEnvironment> - template <typename TParticle> - inline GrammageType NuclearInteraction<TEnvironment>::getInteractionLength( - TParticle const& projectile) { - - // coordinate system, get global frame of reference - - const Code corsikaBeamId = projectile.getPID(); - - if (!is_nucleus(corsikaBeamId)) { - // no nuclear interaction - return std::numeric_limits<double>::infinity() * 1_g / (1_cm * 1_cm); - } - - // read from cross section code table - - MomentumVector pLab = projectile.getMomentum(); - CoordinateSystemPtr const& labCS = pLab.getCoordinateSystem(); - - // FOR NOW: assume target is at rest - MomentumVector pTarget(labCS, {0.0_GeV, 0.0_GeV, 0.0_GeV}); - - // total momentum and energy - HEPEnergyType Elab = projectile.getEnergy() + constants::nucleonMass; - int const nuclA = get_nucleus_A(corsikaBeamId); - auto const ElabNuc = projectile.getEnergy() / nuclA; - - MomentumVector pTotLab(labCS, {0.0_GeV, 0.0_GeV, 0.0_GeV}); - pTotLab += pLab; - pTotLab += pTarget; - auto const pTotLabNorm = pTotLab.getNorm(); - // calculate cm. energy - [[maybe_unused]] HEPEnergyType const ECoM = sqrt( - (Elab + pTotLabNorm) * (Elab - pTotLabNorm)); // binomial for numerical accuracy - auto const ECoMNN = sqrt(2. * ElabNuc * constants::nucleonMass); - CORSIKA_LOG_DEBUG( - "NuclearInteraction: LambdaInt: \n" - " input energy: {}GeV\n" - " input energy CoM: {}GeV\n" - " beam pid: {}\n" - " beam A: {}\n" - " input energy per nucleon: {}GeV\n" - " input energy CoM per nucleon: {}GeV ", - Elab / 1_GeV, ECoM / 1_GeV, get_name(corsikaBeamId), nuclA, ElabNuc / 1_GeV, - ECoMNN / 1_GeV); - - // energy limits - // TODO: values depend on hadronic interaction model !! this is sibyll specific - if (ElabNuc >= 8.5_GeV && ECoMNN >= gMinEnergyPerNucleonCoM_ && - ECoMNN < gMaxEnergyPerNucleonCoM_) { - - // get target from environment - /* - the target should be defined by the Environment, - ideally as full particle object so that the four momenta - and the boosts can be defined.. - */ - auto const* const currentNode = projectile.getNode(); - auto const& mediumComposition = - currentNode->getModelProperties().getNuclearComposition(); - // determine average interaction length - // weighted sum - int i = -1; - CrossSectionType weightedProdCrossSection = 0_mb; - // get weights of components from environment/medium - const auto& w = mediumComposition.getFractions(); - // loop over components in medium - for (auto const targetId : mediumComposition.getComponents()) { - if (targetId == Code::Argon) continue; // NEED TO IGNORE Argon .... - i++; - CORSIKA_LOG_DEBUG("NuclearInteraction: get interaction length for target: {}", - get_name(targetId)); - auto const [productionCrossSection, elaCrossSection] = - getCrossSection(projectile, targetId); - [[maybe_unused]] auto& dummy_elaCrossSection = elaCrossSection; - - CORSIKA_LOG_DEBUG( - "NuclearInteraction: " - "IntLength: nuclib return (mb): " + - std::to_string(productionCrossSection / 1_mb)); - weightedProdCrossSection += w[i] * productionCrossSection; - } - CORSIKA_LOG_DEBUG( - "NuclearInteraction: " - "IntLength: weighted CrossSection (mb): {} ", - weightedProdCrossSection / 1_mb); - - // calculate interaction length in medium - GrammageType const int_length = mediumComposition.getAverageMassNumber() * - constants::u / weightedProdCrossSection; - CORSIKA_LOG_DEBUG( - "NuclearInteraction: " - "interaction length (g/cm2): {} ", - int_length * (1_cm * 1_cm / (0.001_kg))); - - return int_length; - } else { - return std::numeric_limits<double>::infinity() * 1_g / (1_cm * 1_cm); - } - } - - template <typename TEnvironment> - template <typename TSecondaryView> - inline void NuclearInteraction<TEnvironment>::doInteraction(TSecondaryView& view) { - - auto projectile = view.getProjectile(); - - // this routine superimposes different nucleon-nucleon interactions - // in a nucleus-nucleus interaction, based the SIBYLL routine SIBNUC - - const auto ProjId = projectile.getPID(); - // TODO: calculate projectile mass in nuclearStackExtension - // const auto ProjMass = projectile.getMass(); - - CORSIKA_LOG_DEBUG("NuclearInteraction: DoInteraction: called with: {}", - get_name(ProjId)); - - // check if target-style nucleus (enum) - if (!is_nucleus(ProjId)) { - throw std::runtime_error( - "NuclearInteraction: DoInteraction: Wrong nucleus type. Nuclear projectiles " - "should use NuclearStackExtension!"); - } - - auto const ProjMass = get_mass(ProjId); - CORSIKA_LOG_DEBUG("NuclearInteraction: projectile mass: {} ", ProjMass / 1_GeV); - - count_++; - - // position and time of interaction, not used in NUCLIB - Point pOrig = projectile.getPosition(); - TimeType tOrig = projectile.getTime(); - - CORSIKA_LOG_DEBUG("Interaction: position of interaction: {}", pOrig.getCoordinates()); - CORSIKA_LOG_DEBUG("Interaction: time: {} ", tOrig / 1_s); - - // projectile nucleon number - const unsigned int kAProj = get_nucleus_A(ProjId); - if (kAProj > getMaxNucleusAProjectile()) - throw std::runtime_error("Projectile nucleus too large for NUCLIB!"); - - // kinematics - // define projectile nucleus - HEPEnergyType const eProjectileLab = projectile.getEnergy(); - MomentumVector const pProjectileLab = projectile.getMomentum(); - FourVector const PprojLab(eProjectileLab, pProjectileLab); - CoordinateSystemPtr const& labCS = pProjectileLab.getCoordinateSystem(); - - CORSIKA_LOG_DEBUG( - "NuclearInteraction: eProj lab: {} " - "pProj lab: {} ", - eProjectileLab / 1_GeV, pProjectileLab.getComponents() / 1_GeV); - - // define projectile nucleon - HEPEnergyType const eProjectileNucLab = eProjectileLab / kAProj; - MomentumVector const pProjectileNucLab = pProjectileLab / kAProj; - FourVector const PprojNucLab(eProjectileNucLab, pProjectileNucLab); - - CORSIKA_LOG_DEBUG( - "NuclearInteraction: eProjNucleon lab (GeV): {} " - "pProjNucleon lab (GeV): {} ", - eProjectileNucLab / 1_GeV, pProjectileNucLab.getComponents() / 1_GeV); - - // define target - // always a nucleon - // target is always at rest - auto const eTargetNucLab = 0_GeV + constants::nucleonMass; - auto const pTargetNucLab = MomentumVector(labCS, 0_GeV, 0_GeV, 0_GeV); - FourVector const PtargNucLab(eTargetNucLab, pTargetNucLab); - - CORSIKA_LOG_DEBUG( - "NuclearInteraction: etarget lab(GeV): {} " - "NuclearInteraction: ptarget lab(GeV): {} ", - eTargetNucLab / 1_GeV, pTargetNucLab.getComponents() / 1_GeV); - - // center-of-mass energy in nucleon-nucleon frame - auto const PtotNN4 = PtargNucLab + PprojNucLab; - HEPEnergyType EcmNN = PtotNN4.getNorm(); - - CORSIKA_LOG_DEBUG("NuclearInteraction: nuc-nuc cm energy: {}", EcmNN / 1_GeV); - - if (!hadronicInteraction_.isValidCoMEnergy(EcmNN)) { - CORSIKA_LOG_DEBUG( - "NuclearInteraction: nuc-nuc. CoM energy too low for hadronic " - "interaction model!"); - throw std::runtime_error("NuclearInteraction: DoInteraction: energy too low!"); - } - - // define boost to NUCLEON-NUCLEON frame - COMBoost const boost(PprojNucLab, constants::nucleonMass); - // boost projecticle - auto const PprojNucCoM = boost.toCoM(PprojNucLab); - - // boost target - auto const PtargNucCoM = boost.toCoM(PtargNucLab); - - CORSIKA_LOG_DEBUG( - "Interaction: ebeam CoM: {} " - ", pbeam CoM: {} ", - PprojNucCoM.getTimeLikeComponent() / 1_GeV, - PprojNucCoM.getSpaceLikeComponents().getComponents() / 1_GeV); - CORSIKA_LOG_DEBUG( - "Interaction: etarget CoM: {}" - ", ptarget CoM: {}", - PtargNucCoM.getTimeLikeComponent() / 1_GeV, - PtargNucCoM.getSpaceLikeComponents().getComponents() / 1_GeV); - - // sample target nucleon number - // - // proton stand-in for nucleon - const auto beamId = Code::Proton; - auto const* const currentNode = projectile.getNode(); - const auto& mediumComposition = - currentNode->getModelProperties().getNuclearComposition(); - CORSIKA_LOG_DEBUG("get nucleon-nucleus cross sections for target materials.."); - // get cross sections for target materials - // using nucleon-target-nucleus cross section!!! - /* - Here we read the cross section from the interaction model again, - should be passed from getInteractionLength if possible - */ - auto const& compVec = mediumComposition.getComponents(); - std::vector<CrossSectionType> cross_section_of_components(compVec.size()); - - for (size_t i = 0; i < compVec.size(); ++i) { - auto const targetId = compVec[i]; - if (targetId == Code::Argon) continue; // NEED TO IGNORE Argon .... - CORSIKA_LOG_DEBUG("target component: {}", get_name(targetId)); - CORSIKA_LOG_DEBUG("beam id: {}", get_name(beamId)); - const auto [sigProd, sigEla] = - hadronicInteraction_.getCrossSection(beamId, targetId, EcmNN); - cross_section_of_components[i] = sigProd; - [[maybe_unused]] auto sigElaCopy = sigEla; // ONLY TO AVOID COMPILER WARNINGS - } - - const auto targetCode = - mediumComposition.sampleTarget(cross_section_of_components, RNG_); - CORSIKA_LOG_DEBUG("Interaction: target selected: {}", get_name(targetCode)); - /* - FOR NOW: allow nuclei with A<18 or protons only. - when medium composition becomes more complex, approximations will have to be - allowed air in atmosphere also contains some Argon. - */ - int kATarget = -1; - if (is_nucleus(targetCode)) - kATarget = get_nucleus_A(targetCode); - else if (targetCode == Code::Proton) - kATarget = 1; - CORSIKA_LOG_DEBUG("NuclearInteraction: nuclib target code: " + - std::to_string(kATarget)); - if (!hadronicInteraction_.isValidTarget(targetCode)) - throw std::runtime_error("target outside range. "); - // end of target sampling - - // superposition - CORSIKA_LOG_DEBUG( - "NuclearInteraction: sampling nuc. multiple interaction structure.. "); - // get nucleon-nucleon cross section - // (needed to determine number of nucleon-nucleon scatterings) - const auto protonId = Code::Proton; - const auto [prodCrossSection, elaCrossSection] = - hadronicInteraction_.getCrossSection(protonId, protonId, EcmNN); - const double sigProd = prodCrossSection / 1_mb; - const double sigEla = elaCrossSection / 1_mb; - // sample number of interactions (only input variables, output in common cnucms) - // nuclear multiple scattering according to glauber (r.i.p.) - int_nuc_(kATarget, kAProj, sigProd, sigEla); - - CORSIKA_LOG_DEBUG( - "number of nucleons in target : {}\n" - "number of wounded nucleons in target : {}\n" - "number of nucleons in projectile : {}\n" - "number of wounded nucleons in project. : {}\n" - "number of inel. nuc.-nuc. interactions : {}\n" - "number of elastic nucleons in target : {}\n" - "number of elastic nucleons in project. : {}\n" - "impact parameter: {}", - kATarget, cnucms_.na, kAProj, cnucms_.nb, cnucms_.ni, cnucms_.nael, cnucms_.nbel, - cnucms_.b); - - // calculate fragmentation - CORSIKA_LOG_DEBUG("calculating nuclear fragments.."); - // number of interactions - // include elastic - const int nElasticNucleons = cnucms_.nbel; - const int nInelNucleons = cnucms_.nb; - const int nIntProj = nInelNucleons + nElasticNucleons; - const double impactPar = cnucms_.b; // only needed to avoid passing common var. - int nFragments = 0; - // number of fragments is limited to 60 - int AFragments[60]; - // call fragmentation routine - // input: target A, projectile A, number of int. nucleons in projectile, impact - // parameter (fm) output: nFragments, AFragments in addition the momenta ar stored - // in pf in common fragments, neglected - fragm_(kATarget, kAProj, nIntProj, impactPar, nFragments, AFragments); - - // this should not occur but well :) (LCOV_EXCL_START) - if (nFragments > (int)getMaxNFragments()) - throw std::runtime_error("Number of nuclear fragments in NUCLIB exceeded!"); - // (LCOV_EXCL_STOP) - - CORSIKA_LOG_DEBUG("number of fragments: " + std::to_string(nFragments)); - CORSIKA_LOG_DEBUG("adding nuclear fragments to particle stack.."); - // put nuclear fragments on corsika stack - for (int j = 0; j < nFragments; ++j) { - CORSIKA_LOG_DEBUG("fragment {}: A={} px={} py={} pz={}", j, AFragments[j], - fragments_.ppp[j][0], fragments_.ppp[j][1], fragments_.ppp[j][2]); - const auto nuclA = AFragments[j]; - // get Z from stability line - const auto nuclZ = int(nuclA / 2.15 + 0.7); - - // TODO: do we need to catch single nucleons?? - Code specCode = Code::Neutron; // sample neutron or proton ? - if (nuclA > 1) specCode = get_nucleus_code(nuclA, nuclZ); - HEPMassType const mass = get_mass(specCode); - - CORSIKA_LOG_DEBUG("NuclearInteraction: adding fragment: {}", get_name(specCode)); - CORSIKA_LOG_DEBUG("NuclearInteraction: A,Z: {}, {}", nuclA, nuclZ); - CORSIKA_LOG_DEBUG("NuclearInteraction: mass: {} GeV", std::to_string(mass / 1_GeV)); - - // CORSIKA 7 way - // spectators inherit momentum from original projectile - const double mass_ratio = mass / ProjMass; - - CORSIKA_LOG_DEBUG("NuclearInteraction: mass ratio " + std::to_string(mass_ratio)); - - auto const Plab = PprojLab * mass_ratio; - - CORSIKA_LOG_DEBUG("NuclearInteraction: fragment momentum: {}", - Plab.getSpaceLikeComponents().getComponents() / 1_GeV); - - projectile.addSecondary( - std::make_tuple(specCode, Plab.getSpaceLikeComponents(), pOrig, tOrig)); - } - - // add elastic nucleons to corsika stack - // TODO: the elastic interaction could be external like the inelastic interaction, - // e.g. use existing ElasticModel - CORSIKA_LOG_DEBUG("adding elastically scattered nucleons to particle stack.."); - for (int j = 0; j < nElasticNucleons; ++j) { - // TODO: sample proton or neutron - auto const elaNucCode = Code::Proton; - - // CORSIKA 7 way - // elastic nucleons inherit momentum from original projectile - // neglecting momentum transfer in interaction - const double mass_ratio = get_mass(elaNucCode) / ProjMass; - auto const Plab = PprojLab * mass_ratio; - - projectile.addSecondary( - std::make_tuple(elaNucCode, Plab.getSpaceLikeComponents(), pOrig, tOrig)); - } - - // add inelastic interactions - CORSIKA_LOG_DEBUG("calculate inelastic nucleon-nucleon interactions.."); - for (int j = 0; j < nInelNucleons; ++j) { - // TODO: sample neutron or proton - auto pCode = Code::Proton; - // temporarily add to stack, will be removed after interaction in DoInteraction - CORSIKA_LOG_DEBUG("inelastic interaction no. {}", j); - typename TSecondaryView::inner_stack_value_type nucleonStack; - auto inelasticNucleon = nucleonStack.addParticle( - std::make_tuple(pCode, PprojNucLab.getSpaceLikeComponents(), pOrig, tOrig)); - inelasticNucleon.setNode(projectile.getNode()); - // create inelastic interaction for each nucleon - CORSIKA_LOG_TRACE("calling HadronicInteraction..."); - // create new StackView for each of the nucleons - TSecondaryView nucleon_secondaries(inelasticNucleon); - // all inner hadronic event generator - hadronicInteraction_.doInteraction(nucleon_secondaries); - for (const auto& pSec : nucleon_secondaries) { - projectile.addSecondary(std::make_tuple(pSec.getPID(), pSec.getMomentum(), - pSec.getPosition(), pSec.getTime())); - } - } - - CORSIKA_LOG_DEBUG("NuclearInteraction: DoInteraction: done"); - } - -} // namespace corsika::sibyll diff --git a/corsika/detail/modules/sibyll/NuclearInteractionModel.inl b/corsika/detail/modules/sibyll/NuclearInteractionModel.inl new file mode 100644 index 0000000000000000000000000000000000000000..4a564c7e96bcc64e059dfec58b43eb3f5b8ed7ac --- /dev/null +++ b/corsika/detail/modules/sibyll/NuclearInteractionModel.inl @@ -0,0 +1,370 @@ +/* + * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu + * + * This software is distributed under the terms of the GNU General Public + * Licence version 3 (GPL Version 3). See file LICENSE for a full version of + * the license. + */ + +#pragma once + +#include <corsika/media/Environment.hpp> +#include <corsika/media/NuclearComposition.hpp> + +#include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/framework/utility/COMBoost.hpp> +#include <corsika/framework/core/Logging.hpp> + +#include <nuclib.hpp> + +namespace corsika::sibyll { + + template <typename TEnvironment, typename TNucleonModel> + inline NuclearInteractionModel<TEnvironment, TNucleonModel>::NuclearInteractionModel( + TNucleonModel& hadint, TEnvironment const& env) + : environment_(env) + , hadronicInteraction_(hadint) { + + // initialize nuclib + // TODO: make sure this does not overlap with sibyll + nuc_nuc_ini_(); + + // initialize cross sections + initializeNuclearCrossSections(); + } + + template <typename TEnvironment, typename TNucleonModel> + inline NuclearInteractionModel<TEnvironment, + TNucleonModel>::~NuclearInteractionModel() { + CORSIKA_LOG_DEBUG("Nuclib::NuclearInteractionModel n={} Nnuc={}", count_, nucCount_); + } + + template <typename TEnvironment, typename TNucleonModel> + inline bool constexpr NuclearInteractionModel<TEnvironment, TNucleonModel>::isValid( + Code const projectileId, Code const targetId, HEPEnergyType const sqrtSnn) const { + + // also depends on underlying model, for Proton/Neutron projectile + if (!hadronicInteraction_.isValid(Code::Proton, targetId, sqrtSnn)) { return false; } + + // projectile limits: + if (!is_nucleus(projectileId)) { return false; } + unsigned int projectileA = get_nucleus_A(projectileId); + if (projectileA > getMaxNucleusAProjectile() || projectileA < 2) { return false; } + return true; + } // namespace corsika::sibyll + + template <typename TEnvironment, typename TNucleonModel> + inline void + NuclearInteractionModel<TEnvironment, TNucleonModel>::printCrossSectionTable( + Code const pCode) const { + if (!hadronicInteraction_.isValid(Code::Proton, pCode, 100_GeV)) { // LCOV_EXCL_START + CORSIKA_LOG_ERROR("Invalid target type {} for hadron interaction model.", pCode); + return; + } // LCOV_EXCL_STOP + + int const k = targetComponentsIndex_.at(pCode); + Code const pNuclei[] = {Code::Helium, Code::Lithium7, Code::Oxygen, + Code::Neon, Code::Argon, Code::Iron}; + + std::ostringstream table; + table << "Nuclear CrossSectionTable pCode=" << pCode << " :\n en/A "; + for (auto& j : pNuclei) table << std::setw(9) << j; + table << "\n"; + + // loop over energy bins + for (unsigned int i = 0; i < getNEnergyBins(); ++i) { + table << " " << i << " "; + + for (auto& n : pNuclei) { + auto const j = get_nucleus_A(n); + table << " " << std::setprecision(5) << std::setw(8) + << cnucsignuc_.sigma[j - 1][k][i]; + } + table << "\n"; + } + CORSIKA_LOG_DEBUG(table.str()); + } + + template <typename TEnvironment, typename TNucleonModel> + inline void + NuclearInteractionModel<TEnvironment, TNucleonModel>::initializeNuclearCrossSections() { + + auto& universe = *(environment_.getUniverse()); + // generate complete list of all nuclei types in universe + + auto const allElementsInUniverse = std::invoke([&]() { + std::set<Code> allElementsInUniverse; + auto collectElements = [&](auto& vtn) { + if (vtn.hasModelProperties()) { + auto const& comp = + vtn.getModelProperties().getNuclearComposition().getComponents(); + for (auto const c : comp) allElementsInUniverse.insert(c); + } + }; + universe.walk(collectElements); + return allElementsInUniverse; + }); + + CORSIKA_LOG_DEBUG("initializing nuclear cross sections..."); + + // loop over target components, at most 4!! + int k = -1; + for (Code const ptarg : allElementsInUniverse) { + ++k; + CORSIKA_LOG_DEBUG("init target component: {} A={}", ptarg, get_nucleus_A(ptarg)); + int const ib = get_nucleus_A(ptarg); + if (!hadronicInteraction_.isValid(Code::Proton, ptarg, 100_GeV)) { + CORSIKA_LOG_ERROR("Invalid target type {} for hadron interaction model.", ptarg); + continue; + } + targetComponentsIndex_.insert(std::pair<Code, int>(ptarg, k)); + // loop over energies, fNEnBins log. energy bins + for (size_t i = 0; i < getNEnergyBins(); ++i) { + // hard coded energy grid, has to be aligned to definition in signuc2!!, no + // comment.. + HEPEnergyType const Ecm = pow(10., 1. + 1. * i) * 1_GeV; + // head-on pp collision: + HEPEnergyType const EcmHalve = Ecm / 2; + HEPMomentumType const pcm = + sqrt(EcmHalve * EcmHalve - Proton::mass * Proton::mass); + CoordinateSystemPtr cs = get_root_CoordinateSystem(); + FourMomentum projectileP4(EcmHalve, {cs, pcm, 0_eV, 0_eV}); + FourMomentum targetP4(EcmHalve, {cs, -pcm, 0_eV, 0_eV}); + // get p-p cross sections + if (!hadronicInteraction_.isValid(Code::Proton, Code::Proton, Ecm)) { + throw std::runtime_error("invalid projectile,target,ecm combination"); + } + auto const [siginel, sigela] = hadronicInteraction_.getCrossSectionInelEla( + Code::Proton, Code::Proton, projectileP4, targetP4); + double const dsig = siginel / 1_mb; + double const dsigela = sigela / 1_mb; + // loop over projectiles, mass numbers from 2 to fMaxNucleusAProjectile + CORSIKA_LOG_TRACE("Ecm={} siginel={} sigela={}", Ecm / 1_GeV, dsig, dsigela); + for (size_t j = 1; j < gMaxNucleusAProjectile_; ++j) { + const int jj = j + 1; + double sig_out, dsig_out, sigqe_out, dsigqe_out; + sigma_mc_(jj, ib, dsig, dsigela, gNSample_, sig_out, dsig_out, sigqe_out, + dsigqe_out); + // write to table + cnucsignuc_.sigma[j][k][i] = sig_out; + cnucsignuc_.sigqe[j][k][i] = sigqe_out; + CORSIKA_LOG_TRACE("nuc A={} sig={} qe={}", j, sig_out, sigqe_out); + } + } + } + CORSIKA_LOG_DEBUG("cross sections for {} components initialized!", + targetComponentsIndex_.size()); + for (auto& ptarg : allElementsInUniverse) { printCrossSectionTable(ptarg); } + } + + template <typename TEnvironment, typename TNucleonModel> + inline CrossSectionType + NuclearInteractionModel<TEnvironment, TNucleonModel>::readCrossSectionTable( + int const ia, Code const pTarget, HEPEnergyType const elabnuc) const { + + int const ib = targetComponentsIndex_.at(pTarget) + 1; // table index in fortran + auto const ECoMNuc = sqrt(2. * constants::nucleonMass * elabnuc); + if (ECoMNuc < getMinEnergyPerNucleonCoM() || ECoMNuc > getMaxEnergyPerNucleonCoM()) { + throw std::runtime_error("energy outside tabulated range!"); + } + double const e0 = elabnuc / 1_GeV; + double sig; + CORSIKA_LOG_DEBUG("ReadCrossSectionTable: {} {} {}", ia, ib, e0); + signuc2_(ia, ib, e0, sig); + CORSIKA_LOG_DEBUG("ReadCrossSectionTable: sig={}", sig); + return sig * 1_mb; + } + + template <typename TEnvironment, typename TNucleonModel> + CrossSectionType inline NuclearInteractionModel< + TEnvironment, TNucleonModel>::getCrossSection(Code const projectileId, + Code const targetId, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) const { + + HEPEnergyType const sqrtSnn = (projectileP4 + targetP4).getNorm(); + if (!isValid(projectileId, targetId, sqrtSnn)) { return CrossSectionType::zero(); } + HEPEnergyType const LabEnergyPerNuc = + static_pow<2>(sqrtSnn) / (2 * constants::nucleonMass); + auto const sigProd = + readCrossSectionTable(get_nucleus_A(projectileId), targetId, LabEnergyPerNuc); + CORSIKA_LOG_DEBUG("cross section (mb): {}", sigProd / 1_mb); + return sigProd; + } + + template <typename TEnvironment, typename TNucleonModel> + template <typename TSecondaryView> + inline void NuclearInteractionModel<TEnvironment, TNucleonModel>::doInteraction( + TSecondaryView& view, Code const projectileId, Code const targetId, + FourMomentum const& projectileP4, FourMomentum const& targetP4) { + + // model is only designed for projectile nuclei. Collisions are broken down into + // "nucleon-target" collisions. + if (!is_nucleus(projectileId)) { + throw std::runtime_error("Can only handle nuclear projectiles."); + } + size_t const projectileA = get_nucleus_A(projectileId); + + // this is center-of-mass for projectile_nucleon - target + FourMomentum const nucleonP4 = projectileP4 / projectileA; + HEPEnergyType const sqrtSnucleon = (nucleonP4 + targetP4).getNorm(); + if (!isValid(projectileId, targetId, sqrtSnucleon)) { + throw std::runtime_error("Invalid projectile/target/energy combination."); + } + // projectile is always nucleus! + // Elab corresponding to sqrtSnucleon -> fixed target projectile + COMBoost const boost(nucleonP4, targetP4); + + CORSIKA_LOG_DEBUG("pId={} tId={} sqrtSnucleon={}GeV Aproj={}", projectileId, targetId, + sqrtSnucleon / 1_GeV, projectileA); + count_++; + + // lab. momentum per projectile nucleon + HEPMomentumType const pNucleonLab = nucleonP4.getSpaceLikeComponents().getNorm(); + // nucleon momentum in direction of CM motion (lab system) + MomentumVector const p3NucleonLab(boost.getRotatedCS(), {0_GeV, 0_GeV, pNucleonLab}); + + /* + FOR NOW: allow nuclei with A<18 or protons/nucleon only. + when medium composition becomes more complex, approximations will have to be + allowed air in atmosphere also contains some Argon. + */ + int kATarget = -1; + size_t targetA = 1; + if (is_nucleus(targetId)) { + kATarget = get_nucleus_A(targetId); + targetA = kATarget; + } else if (targetId == Code::Proton || targetId == Code::Neutron || + targetId == Code::Hydrogen) { + kATarget = 1; + } + CORSIKA_LOG_DEBUG("nuclib target code: {}", kATarget); + + // end of target sampling + + // superposition + CORSIKA_LOG_DEBUG("sampling nuc. multiple interaction structure.. "); + // get nucleon-nucleon cross section + // (needed to determine number of nucleon-nucleon scatterings) + auto const protonId = Code::Proton; + auto const [prodCrossSection, elaCrossSection] = + hadronicInteraction_.getCrossSectionInelEla( + protonId, protonId, nucleonP4, + targetP4 / targetA); // todo check, wrong RU + double const sigProd = prodCrossSection / 1_mb; + double const sigEla = elaCrossSection / 1_mb; + // sample number of interactions (only input variables, output in common cnucms) + // nuclear multiple scattering according to glauber (r.i.p.) + int_nuc_(kATarget, projectileA, sigProd, sigEla); + + CORSIKA_LOG_DEBUG( + "number of nucleons in target : {}\n" + "number of wounded nucleons in target : {}\n" + "number of nucleons in projectile : {}\n" + "number of wounded nucleons in project. : {}\n" + "number of inel. nuc.-nuc. interactions : {}\n" + "number of elastic nucleons in target : {}\n" + "number of elastic nucleons in project. : {}\n" + "impact parameter: {}", + kATarget, cnucms_.na, projectileA, cnucms_.nb, cnucms_.ni, cnucms_.nael, + cnucms_.nbel, cnucms_.b); + + // calculate fragmentation + CORSIKA_LOG_DEBUG("calculating nuclear fragments.."); + // number of interactions + // include elastic + int const nElasticNucleons = cnucms_.nbel; + int const nInelNucleons = cnucms_.nb; + int const nIntProj = nInelNucleons + nElasticNucleons; + double const impactPar = cnucms_.b; // only needed to avoid passing common var. + int nFragments = 0; + // number of fragments is limited to 60 + int AFragments[60]; + // call fragmentation routine + // input: target A, projectile A, number of int. nucleons in projectile, impact + // parameter (fm) output: nFragments, AFragments in addition the momenta ar stored + // in pf in common fragments, neglected + fragm_(kATarget, projectileA, nIntProj, impactPar, nFragments, AFragments); + + // this should not occur but well :) (LCOV_EXCL_START) + if (nFragments > (int)getMaxNFragments()) { + throw std::runtime_error("Number of nuclear fragments in NUCLIB exceeded!"); + } + // (LCOV_EXCL_STOP) + + // position and time of interaction, not used in NUCLIB + auto const& projectile = view.parent(); + // position and time of interaction, not used in NUCLI + Point const& pOrig = projectile.getPosition(); + TimeType const delay = projectile.getTime(); + + CORSIKA_LOG_DEBUG("Interaction: position of interaction: {}, {} ns", + pOrig.getCoordinates(), delay / 1_ns); + CORSIKA_LOG_DEBUG("number of fragments: {}", nFragments); + CORSIKA_LOG_DEBUG("adding nuclear fragments to particle stack.."); + // put nuclear fragments on corsika stack + for (int j = 0; j < nFragments; ++j) { + CORSIKA_LOG_DEBUG("fragment {}: A={} px={} py={} pz={}", j, AFragments[j], + fragments_.ppp[j][0], fragments_.ppp[j][1], fragments_.ppp[j][2]); + auto const nuclA = AFragments[j]; + // get Z from stability line + auto const nuclZ = int(nuclA / 2.15 + 0.7); + + // TODO: do we need to catch single nucleons?? + Code const specCode = (nuclA == 1 ? + // TODO: sample neutron or proton + Code::Proton + : get_nucleus_code(nuclA, nuclZ)); + HEPMassType const mass = get_mass(specCode); + + CORSIKA_LOG_DEBUG("adding fragment: {}", get_name(specCode)); + CORSIKA_LOG_DEBUG("A,Z: {}, {}", nuclA, nuclZ); + CORSIKA_LOG_DEBUG("mass: {} GeV", mass / 1_GeV); + + // CORSIKA 7 way + // spectators inherit momentum from original projectile + auto const p3lab = p3NucleonLab * nuclA; + CORSIKA_LOG_DEBUG("fragment momentum {}", p3lab.getComponents() / 1_GeV); + view.addSecondary(std::make_tuple(specCode, p3lab, pOrig, delay)); + } + + // add elastic nucleons to corsika stack + // TODO: the elastic interaction could be external like the inelastic interaction, + // e.g. use existing ElasticModel + CORSIKA_LOG_DEBUG("adding elastically scattered nucleons to particle stack.."); + for (int j = 0; j < nElasticNucleons; ++j) { + // TODO: sample proton or neutron + Code const elaNucCode = Code::Proton; + + // CORSIKA 7 way + // elastic nucleons inherit momentum from original projectile + // neglecting momentum transfer in interaction + auto const p3lab = p3NucleonLab; + view.addSecondary(std::make_tuple(elaNucCode, p3lab, pOrig, delay)); + } + + // add inelastic interactions + CORSIKA_LOG_DEBUG("calculate inelastic nucleon-nucleon interactions.."); + for (int j = 0; j < nInelNucleons; ++j) { + // TODO: sample neutron or proton + auto const pCode = Code::Proton; + // temporarily add to stack, will be removed after interaction in DoInteraction + CORSIKA_LOG_DEBUG("inelastic interaction no. {}", j); + typename TSecondaryView::inner_stack_value_type nucleonStack; + auto inelasticNucleon = + nucleonStack.addParticle(std::make_tuple(pCode, p3NucleonLab, pOrig, delay)); + inelasticNucleon.setNode(view.getProjectile().getNode()); + // create inelastic interaction for each nucleon + CORSIKA_LOG_TRACE("calling HadronicInteraction..."); + // create new StackView for each of the nucleons + TSecondaryView nucleon_secondaries(inelasticNucleon); + // all inner hadronic event generator + hadronicInteraction_.doInteraction(nucleon_secondaries, pCode, targetId, nucleonP4, + targetP4); + for (const auto& pSec : nucleon_secondaries) { + view.addSecondary(std::make_tuple(pSec.getPID(), pSec.getMomentum(), + pSec.getPosition(), pSec.getTime())); + } + } + } +} // namespace corsika::sibyll diff --git a/corsika/detail/modules/urqmd/ParticleConversion.inl b/corsika/detail/modules/urqmd/ParticleConversion.inl new file mode 100644 index 0000000000000000000000000000000000000000..de533d0488df20a2f376c6ad876109d57270c102 --- /dev/null +++ b/corsika/detail/modules/urqmd/ParticleConversion.inl @@ -0,0 +1,104 @@ +/* + * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu + * + * This software is distributed under the terms of the GNU General Public + * Licence version 3 (GPL Version 3). See file LICENSE for a full version of + * the license. + */ + +#pragma once + +#include <corsika/modules/urqmd/ParticleConversion.hpp> + +#include <corsika/framework/core/ParticleProperties.hpp> +#include <corsika/framework/core/PhysicalUnits.hpp> + +#include <urqmd.hpp> + +namespace corsika::urqmd { + + inline bool canInteract(Code const vCode) { + // According to the manual, UrQMD can use all mesons, baryons and nucleons + // which are modeled also as input particles. I think it is safer to accept + // only the usual long-lived species as input. + // TODO: Charmed mesons should be added to the list, too + + static std::array constexpr validProjectileCodes{ + Code::Proton, Code::AntiProton, Code::Neutron, Code::AntiNeutron, Code::PiPlus, + Code::PiMinus, Code::KPlus, Code::KMinus, Code::K0Short, Code::K0Long}; + + return std::find(std::cbegin(validProjectileCodes), std::cend(validProjectileCodes), + vCode) != std::cend(validProjectileCodes); + } + + inline std::pair<int, int> convertToUrQMD(Code const code) { + static const std::map<int, std::pair<int, int>> mapPDGToUrQMD{ + // data mostly from github.com/afedynitch/ParticleDataTool + {22, {100, 0}}, // photon + {111, {101, 0}}, // pi0 + {211, {101, 2}}, // pi+ + {-211, {101, -2}}, // pi- + {321, {106, 1}}, // K+ + {-321, {-106, -1}}, // K- + {311, {106, -1}}, // K0 + {-311, {-106, 1}}, // K0bar + {2212, {1, 1}}, // p + {2112, {1, -1}}, // n + {-2212, {-1, -1}}, // pbar + {-2112, {-1, 1}}, // nbar + {221, {102, 0}}, // eta + {213, {104, 2}}, // rho+ + {-213, {104, -2}}, // rho- + {113, {104, 0}}, // rho0 + {323, {108, 2}}, // K*+ + {-323, {108, -2}}, // K*- + {313, {108, 0}}, // K*0 + {-313, {-108, 0}}, // K*0-bar + {223, {103, 0}}, // omega + {333, {109, 0}}, // phi + {3222, {40, 2}}, // Sigma+ + {3212, {40, 0}}, // Sigma0 + {3112, {40, -2}}, // Sigma- + {3322, {49, 0}}, // Xi0 + {3312, {49, -1}}, // Xi- + {3122, {27, 0}}, // Lambda0 + {2224, {17, 4}}, // Delta++ + {2214, {17, 2}}, // Delta+ + {2114, {17, 0}}, // Delta0 + {1114, {17, -2}}, // Delta- + {3224, {41, 2}}, // Sigma*+ + {3214, {41, 0}}, // Sigma*0 + {3114, {41, -2}}, // Sigma*- + {3324, {50, 0}}, // Xi*0 + {3314, {50, -1}}, // Xi*- + {3334, {55, 0}}, // Omega- + {411, {133, 2}}, // D+ + {-411, {133, -2}}, // D- + {421, {133, 0}}, // D0 + {-421, {-133, 0}}, // D0-bar + {441, {107, 0}}, // etaC + {431, {138, 1}}, // Ds+ + {-431, {138, -1}}, // Ds- + {433, {139, 1}}, // Ds*+ + {-433, {139, -1}}, // Ds*- + {413, {134, 1}}, // D*+ + {-413, {134, -1}}, // D*- + {10421, {134, 0}}, // D*0 + {-10421, {-134, 0}}, // D*0-bar + {443, {135, 0}}, // jpsi + }; + + return mapPDGToUrQMD.at(static_cast<int>(get_PDG(code))); + } + + inline Code convertFromUrQMD(int vItyp, int vIso3) { + int const pdgInt = + ::urqmd::pdgid_(vItyp, vIso3); // use the conversion function provided by UrQMD + if (pdgInt == 0) { // ::urqmd::pdgid_ returns 0 on error + throw std::runtime_error("UrQMD pdgid() returned 0"); + } + auto const pdg = static_cast<PDGCode>(pdgInt); + return convert_from_PDG(pdg); + } + +} // namespace corsika::urqmd diff --git a/corsika/detail/modules/urqmd/UrQMD.inl b/corsika/detail/modules/urqmd/UrQMD.inl index 80da719edae6312513999344fae7b819999980b9..1bf0f0d59a42428df231717f2df869f72c30bd45 100644 --- a/corsika/detail/modules/urqmd/UrQMD.inl +++ b/corsika/detail/modules/urqmd/UrQMD.inl @@ -9,11 +9,13 @@ #pragma once #include <corsika/modules/urqmd/UrQMD.hpp> +#include <corsika/modules/urqmd/ParticleConversion.hpp> #include <corsika/framework/core/ParticleProperties.hpp> #include <corsika/framework/core/PhysicalUnits.hpp> #include <corsika/framework/geometry/QuantityVector.hpp> #include <corsika/framework/geometry/Vector.hpp> +#include <corsika/framework/utility/COMBoost.hpp> #include <boost/filesystem.hpp> #include <boost/multi_array.hpp> @@ -35,12 +37,21 @@ namespace corsika::urqmd { ::urqmd::iniurqmdc8_(); } - inline CrossSectionType UrQMD::getTabulatedCrossSection(Code projectileCode, - Code targetCode, - HEPEnergyType labEnergy) const { + inline bool UrQMD::isValid(Code const projectileId, Code const targetId) const { + + if (!is_hadron(projectileId) || !corsika::urqmd::canInteract(projectileId)) { + return false; + } + if (!is_nucleus(targetId)) { return false; } + return true; + } + + inline CrossSectionType UrQMD::getTabulatedCrossSection( + Code const projectileId, Code const targetId, HEPEnergyType const labEnergy) const { + // translated to C++ from CORSIKA 7 subroutine cxtot_u - auto const kinEnergy = labEnergy - get_mass(projectileCode); + auto const kinEnergy = labEnergy - get_mass(projectileId); assert(kinEnergy >= HEPEnergyType::zero()); @@ -54,7 +65,7 @@ namespace corsika::urqmd { w[2 - 1] = w[2 - 1] - 2 * w[3 - 1]; int projectileIndex; - switch (projectileCode) { + switch (projectileId) { case Code::Proton: projectileIndex = 0; break; @@ -88,14 +99,14 @@ namespace corsika::urqmd { projectileIndex = 8; break; default: { // LCOV_EXCL_START since this can never happen due to canInteract - CORSIKA_LOG_WARN("UrQMD cross-section not tabulated for {}", projectileCode); + CORSIKA_LOG_WARN("UrQMD cross-section not tabulated for {}", projectileId); return CrossSectionType::zero(); // LCOV_EXCL_STOP } } int targetIndex; - switch (targetCode) { + switch (targetId) { case Code::Nitrogen: targetIndex = 0; break; @@ -107,7 +118,7 @@ namespace corsika::urqmd { break; default: std::stringstream ss; - ss << "UrQMD cross-section not tabluated for target " << targetCode; + ss << "UrQMD cross-section not tabluated for target " << targetId; throw std::runtime_error(ss.str().data()); } @@ -119,53 +130,17 @@ namespace corsika::urqmd { CORSIKA_LOG_TRACE( "UrQMD::GetTabulatedCrossSection proj={}, targ={}, E={} GeV, sigma={}", - get_name(projectileCode), get_name(targetCode), labEnergy / 1_GeV, result); + get_name(projectileId), get_name(targetId), labEnergy / 1_GeV, result); return result; } - inline CrossSectionType UrQMD::getCrossSection(Code vProjectileCode, Code vTargetCode, - HEPEnergyType vLabEnergy, - int vAProjectile = 1) { - - // the following is a translation of ptsigtot() into C++ - if (!is_nucleus(vProjectileCode) && - !is_nucleus(vTargetCode)) { // both particles are "special" - auto const mProj = get_mass(vProjectileCode); - auto const mTar = get_mass(vTargetCode); - double sqrtS = - sqrt(static_pow<2>(mProj) + static_pow<2>(mTar) + 2 * vLabEnergy * mTar) * - (1 / 1_GeV); - - // we must set some UrQMD globals first... - auto const [ityp, iso3] = convertToUrQMD(vProjectileCode); - ::urqmd::inputs_.spityp[0] = ityp; - ::urqmd::inputs_.spiso3[0] = iso3; - - auto const [itypTar, iso3Tar] = convertToUrQMD(vTargetCode); - ::urqmd::inputs_.spityp[1] = itypTar; - ::urqmd::inputs_.spiso3[1] = iso3Tar; - - int one = 1; - int two = 2; - return ::urqmd::sigtot_(one, two, sqrtS) * 1_mb; - } else { - int const Ap = vAProjectile; - int const At = is_nucleus(vTargetCode) ? get_nucleus_A(vTargetCode) : 1; - - double const maxImpact = ::urqmd::nucrad_(Ap) + ::urqmd::nucrad_(At) + - 2 * ::urqmd::options_.CTParam[30 - 1]; - return 10_mb * M_PI * static_pow<2>(maxImpact); - // is a constant cross-section really reasonable? - } - } - - template <typename TParticle> - inline CrossSectionType UrQMD::getCrossSection(TParticle const& projectile, - Code targetCode) const { - auto const projectileCode = projectile.getPID(); + inline CrossSectionType UrQMD::getCrossSection(Code const projectileId, + Code const targetId, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) const { - if (is_nucleus(projectileCode)) { + if (!isValid(projectileId, targetId)) { /* * unfortunately unavoidable at the moment until we have tools to get the actual * inealstic cross-section from UrQMD @@ -173,72 +148,61 @@ namespace corsika::urqmd { return CrossSectionType::zero(); } - return getTabulatedCrossSection(projectileCode, targetCode, projectile.getEnergy()); - } - - inline bool UrQMD::canInteract(Code vCode) const { - // According to the manual, UrQMD can use all mesons, baryons and nucleons - // which are modeled also as input particles. I think it is safer to accept - // only the usual long-lived species as input. - // TODO: Charmed mesons should be added to the list, too + // define projectile, in lab frame + auto const sqrtS2 = (projectileP4 + targetP4).getNormSqr(); + HEPEnergyType const Elab = (sqrtS2 - static_pow<2>(get_mass(projectileId)) - + static_pow<2>(get_mass(targetId))) / + (2 * get_mass(targetId)); - static std::array constexpr validProjectileCodes{ - Code::Proton, Code::AntiProton, Code::Neutron, Code::AntiNeutron, Code::PiPlus, - Code::PiMinus, Code::KPlus, Code::KMinus, Code::K0Short, Code::K0Long}; + bool const tabulated = true; + if (tabulated) { return getTabulatedCrossSection(projectileId, targetId, Elab); } - return std::find(std::cbegin(validProjectileCodes), std::cend(validProjectileCodes), - vCode) != std::cend(validProjectileCodes); - } + // the following is a translation of ptsigtot() into C++ + if (!is_nucleus(projectileId) && + !is_nucleus(targetId)) { // both particles are "special" - template <typename TParticle> - inline GrammageType UrQMD::getInteractionLength(TParticle const& vParticle) const { + double sqrtS = sqrt(sqrtS2) / 1_GeV; - if (!canInteract(vParticle.getPID())) { - // we could do the canInteract check in getCrossSection, too but if - // we do it here we have the advantage of avoiding the loop - return std::numeric_limits<double>::infinity() * 1_g / (1_cm * 1_cm); - } + // we must set some UrQMD globals first... + auto const [ityp, iso3] = corsika::urqmd::convertToUrQMD(projectileId); + ::urqmd::inputs_.spityp[0] = ityp; + ::urqmd::inputs_.spiso3[0] = iso3; - auto const& mediumComposition = - vParticle.getNode()->getModelProperties().getNuclearComposition(); - using namespace std::placeholders; + auto const [itypTar, iso3Tar] = corsika::urqmd::convertToUrQMD(targetId); + ::urqmd::inputs_.spityp[1] = itypTar; + ::urqmd::inputs_.spiso3[1] = iso3Tar; - CrossSectionType const weightedProdCrossSection = mediumComposition.getWeightedSum( - std::bind(&UrQMD::getCrossSection<decltype(vParticle)>, this, vParticle, _1)); + int one = 1; + int two = 2; + return ::urqmd::sigtot_(one, two, sqrtS) * 1_mb; + } - return mediumComposition.getAverageMassNumber() * constants::u / - weightedProdCrossSection; + // at least one of them is a nucleus + int const Ap = is_nucleus(projectileId) ? get_nucleus_A(projectileId) : 1; + int const At = is_nucleus(targetId) ? get_nucleus_A(targetId) : 1; + double const maxImpact = ::urqmd::nucrad_(Ap) + ::urqmd::nucrad_(At) + + 2 * ::urqmd::options_.CTParam[30 - 1]; + return 10_mb * M_PI * static_pow<2>(maxImpact); + // is a constant cross-section really reasonable? } template <typename TView> - inline void UrQMD::doInteraction(TView& view) { - - auto projectile = view.getProjectile(); - - Code projectileCode = projectile.getPID(); - auto const projectileEnergyLab = projectile.getEnergy(); - auto const& projectileMomentumLab = projectile.getMomentum(); - auto const& projectilePosition = projectile.getPosition(); - auto const projectileTime = projectile.getTime(); - - // sample target particle - auto const& mediumComposition = - projectile.getNode()->getModelProperties().getNuclearComposition(); - auto const componentCrossSections = std::invoke([&]() { - auto const& components = mediumComposition.getComponents(); - std::vector<CrossSectionType> crossSections; - crossSections.reserve(components.size()); - - for (auto const c : components) { - crossSections.push_back(getCrossSection(projectile, c)); - } - - return crossSections; - }); + inline void UrQMD::doInteraction(TView& view, Code const projectileId, + Code const targetId, FourMomentum const& projectileP4, + FourMomentum const& targetP4) { + + // define projectile, in lab frame + auto const sqrtS2 = (projectileP4 + targetP4).getNormSqr(); + HEPEnergyType const Elab = (sqrtS2 - static_pow<2>(get_mass(projectileId)) - + static_pow<2>(get_mass(targetId))) / + (2 * get_mass(targetId)); + + if (!isValid(projectileId, targetId)) { + throw std::runtime_error("invalid target,projectile,energy combination"); + } - auto const targetCode = mediumComposition.sampleTarget(componentCrossSections, RNG_); - auto const targetA = get_nucleus_A(targetCode); - auto const targetZ = get_nucleus_Z(targetCode); + size_t const targetA = get_nucleus_A(targetId); + size_t const targetZ = get_nucleus_Z(targetId); ::urqmd::inputs_.nevents = 1; ::urqmd::sys_.eos = 0; // could be configurable in principle @@ -246,14 +210,13 @@ namespace corsika::urqmd { ::urqmd::sys_.nsteps = 1; // initialization regarding projectile - if (is_nucleus(projectileCode)) { + if (is_nucleus(projectileId)) { // is this everything? ::urqmd::inputs_.prspflg = 0; - ::urqmd::sys_.Ap = get_nucleus_A(projectileCode); - ::urqmd::sys_.Zp = get_nucleus_Z(projectileCode); - ::urqmd::rsys_.ebeam = - (projectileEnergyLab - projectile.getMass()) * (1 / 1_GeV) / ::urqmd::sys_.Ap; + ::urqmd::sys_.Ap = get_nucleus_A(projectileId); + ::urqmd::sys_.Zp = get_nucleus_Z(projectileId); + ::urqmd::rsys_.ebeam = (Elab - get_mass(projectileId)) / 1_GeV / ::urqmd::sys_.Ap; ::urqmd::rsys_.bdist = ::urqmd::nucrad_(targetA) + ::urqmd::nucrad_(::urqmd::sys_.Ap) + @@ -267,20 +230,19 @@ namespace corsika::urqmd { 1; // even for non-baryons this has to be set, see vanilla UrQMD.f ::urqmd::rsys_.bdist = ::urqmd::nucrad_(targetA) + ::urqmd::nucrad_(1) + 2 * ::urqmd::options_.CTParam[30 - 1]; - ::urqmd::rsys_.ebeam = (projectileEnergyLab - projectile.getMass()) * (1 / 1_GeV); + ::urqmd::rsys_.ebeam = (Elab - get_mass(projectileId)) / 1_GeV; - if (projectileCode == Code::K0Long || projectileCode == Code::K0Short) { - projectileCode = booleanDist_(RNG_) ? Code::K0 : Code::K0Bar; - } - - auto const [ityp, iso3] = convertToUrQMD(projectileCode); + auto const [ityp, iso3] = corsika::urqmd::convertToUrQMD( + (projectileId == Code::K0Long || projectileId == Code::K0Short) + ? (booleanDist_(RNG_) ? Code::K0 : Code::K0Bar) + : projectileId); // todo: conversion of K_long/short into strong eigenstates; ::urqmd::inputs_.spityp[0] = ityp; ::urqmd::inputs_.spiso3[0] = iso3; } - // initilazation regarding target - if (is_nucleus(targetCode)) { + // initialization regarding target + if (is_nucleus(targetId)) { ::urqmd::sys_.Zt = targetZ; ::urqmd::sys_.At = targetA; ::urqmd::inputs_.trspflg = 0; // nucleus as target @@ -288,7 +250,7 @@ namespace corsika::urqmd { ::urqmd::cascinit_(::urqmd::sys_.Zt, ::urqmd::sys_.At, id); } else { ::urqmd::inputs_.trspflg = 1; // special particle as target - auto const [ityp, iso3] = convertToUrQMD(targetCode); + auto const [ityp, iso3] = corsika::urqmd::convertToUrQMD(targetId); ::urqmd::inputs_.spityp[1] = ityp; ::urqmd::inputs_.spiso3[1] = iso3; } @@ -297,103 +259,36 @@ namespace corsika::urqmd { iflb_; // flag for retrying interaction in case of empty event, 0 means retry ::urqmd::urqmd_(iflb); + auto projectile = view.getProjectile(); + auto const& projectilePosition = projectile.getPosition(); + auto const projectileTime = projectile.getTime(); + // now retrieve secondaries from UrQMD - auto const& originalCS = projectileMomentumLab.getCoordinateSystem(); - CoordinateSystemPtr const& zAxisFrame = - make_rotationToZ(originalCS, projectileMomentumLab); + COMBoost const boost(projectileP4, targetP4); + auto const& originalCS = boost.getOriginalCS(); + auto const& csPrime = boost.getRotatedCS(); for (int i = 0; i < ::urqmd::sys_.npart; ++i) { - auto code = convertFromUrQMD(::urqmd::isys_.ityp[i], ::urqmd::isys_.iso3[i]); + auto code = corsika::urqmd::convertFromUrQMD(::urqmd::isys_.ityp[i], + ::urqmd::isys_.iso3[i]); if (code == Code::K0 || code == Code::K0Bar) { code = booleanDist_(RNG_) ? Code::K0Short : Code::K0Long; } // "coor_.p0[i] * 1_GeV" is likely off-shell as UrQMD doesn't preserve masses well - auto momentum = - Vector(zAxisFrame, - QuantityVector<dimensionless_d>{ - ::urqmd::coor_.px[i], ::urqmd::coor_.py[i], ::urqmd::coor_.pz[i]} * - 1_GeV); + MomentumVector momentum{csPrime, + {::urqmd::coor_.px[i] * 1_GeV, ::urqmd::coor_.py[i] * 1_GeV, + ::urqmd::coor_.pz[i] * 1_GeV}}; momentum.rebase(originalCS); // transform back into standard lab frame CORSIKA_LOG_DEBUG(" {} {} {} ", i, code, momentum.getComponents()); - projectile.addSecondary( + view.addSecondary( std::make_tuple(code, momentum, projectilePosition, projectileTime)); } CORSIKA_LOG_DEBUG("UrQMD generated {} secondaries!", ::urqmd::sys_.npart); } - inline Code convertFromUrQMD(int vItyp, int vIso3) { - int const pdgInt = - ::urqmd::pdgid_(vItyp, vIso3); // use the conversion function provided by UrQMD - if (pdgInt == 0) { // ::urqmd::pdgid_ returns 0 on error - throw std::runtime_error("UrQMD pdgid() returned 0"); - } - auto const pdg = static_cast<PDGCode>(pdgInt); - return convert_from_PDG(pdg); - } - - inline std::pair<int, int> convertToUrQMD(Code code) { - static const std::map<int, std::pair<int, int>> mapPDGToUrQMD{ - // data mostly from github.com/afedynitch/ParticleDataTool - {22, {100, 0}}, // photon - {111, {101, 0}}, // pi0 - {211, {101, 2}}, // pi+ - {-211, {101, -2}}, // pi- - {321, {106, 1}}, // K+ - {-321, {-106, -1}}, // K- - {311, {106, -1}}, // K0 - {-311, {-106, 1}}, // K0bar - {2212, {1, 1}}, // p - {2112, {1, -1}}, // n - {-2212, {-1, -1}}, // pbar - {-2112, {-1, 1}}, // nbar - {221, {102, 0}}, // eta - {213, {104, 2}}, // rho+ - {-213, {104, -2}}, // rho- - {113, {104, 0}}, // rho0 - {323, {108, 2}}, // K*+ - {-323, {108, -2}}, // K*- - {313, {108, 0}}, // K*0 - {-313, {-108, 0}}, // K*0-bar - {223, {103, 0}}, // omega - {333, {109, 0}}, // phi - {3222, {40, 2}}, // Sigma+ - {3212, {40, 0}}, // Sigma0 - {3112, {40, -2}}, // Sigma- - {3322, {49, 0}}, // Xi0 - {3312, {49, -1}}, // Xi- - {3122, {27, 0}}, // Lambda0 - {2224, {17, 4}}, // Delta++ - {2214, {17, 2}}, // Delta+ - {2114, {17, 0}}, // Delta0 - {1114, {17, -2}}, // Delta- - {3224, {41, 2}}, // Sigma*+ - {3214, {41, 0}}, // Sigma*0 - {3114, {41, -2}}, // Sigma*- - {3324, {50, 0}}, // Xi*0 - {3314, {50, -1}}, // Xi*- - {3334, {55, 0}}, // Omega- - {411, {133, 2}}, // D+ - {-411, {133, -2}}, // D- - {421, {133, 0}}, // D0 - {-421, {-133, 0}}, // D0-bar - {441, {107, 0}}, // etaC - {431, {138, 1}}, // Ds+ - {-431, {138, -1}}, // Ds- - {433, {139, 1}}, // Ds*+ - {-433, {139, -1}}, // Ds*- - {413, {134, 1}}, // D*+ - {-413, {134, -1}}, // D*- - {10421, {134, 0}}, // D*0 - {-10421, {-134, 0}}, // D*0-bar - {443, {135, 0}}, // jpsi - }; - - return mapPDGToUrQMD.at(static_cast<int>(get_PDG(code))); - } - inline void UrQMD::readXSFile(boost::filesystem::path const filename) { boost::filesystem::ifstream file(filename, std::ios::in); diff --git a/corsika/detail/stack/VectorStack.inl b/corsika/detail/stack/VectorStack.inl index 9d2a9434c464df9ff1d7c8b1e1b0cdfc4c5143ff..57bc094a438dc6f7383baa39215434099777690b 100644 --- a/corsika/detail/stack/VectorStack.inl +++ b/corsika/detail/stack/VectorStack.inl @@ -41,7 +41,7 @@ namespace corsika { template <typename StackIteratorInterface> inline void ParticleInterface<StackIteratorInterface>::setParticleData( - ParticleInterface<StackIteratorInterface> const&, + ParticleInterface<StackIteratorInterface> const& parent, particle_data_momentum_type const& v) { this->setPID(std::get<0>(v)); MomentumVector const p = std::get<1>(v); @@ -54,7 +54,7 @@ namespace corsika { this->setDirection(p / sqrt(P2)); } this->setPosition(std::get<2>(v)); - this->setTime(std::get<3>(v)); + this->setTime(std::get<3>(v) + parent.getTime()); // parent time is added } template <typename StackIteratorInterface> @@ -69,12 +69,13 @@ namespace corsika { template <typename StackIteratorInterface> inline void ParticleInterface<StackIteratorInterface>::setParticleData( - ParticleInterface<StackIteratorInterface> const&, particle_data_type const& v) { + ParticleInterface<StackIteratorInterface> const& parent, + particle_data_type const& v) { this->setPID(std::get<0>(v)); this->setKineticEnergy(std::get<1>(v)); this->setDirection(std::get<2>(v)); this->setPosition(std::get<3>(v)); - this->setTime(std::get<4>(v)); + this->setTime(std::get<4>(v) + parent.getTime()); // parent time is added } template <typename StackIteratorInterface> diff --git a/corsika/framework/core/Cascade.hpp b/corsika/framework/core/Cascade.hpp index 1163950f3806050b9fdd04c93aa6905a9c8cc6f0..ac7a899eb52538c432995375ea3657df89add166 100644 --- a/corsika/framework/core/Cascade.hpp +++ b/corsika/framework/core/Cascade.hpp @@ -12,11 +12,12 @@ #include <corsika/framework/process/ProcessReturn.hpp> #include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/framework/core/Logging.hpp> #include <corsika/framework/random/ExponentialDistribution.hpp> #include <corsika/framework/random/RNGManager.hpp> #include <corsika/framework/random/UniformRealDistribution.hpp> #include <corsika/framework/stack/SecondaryView.hpp> -#include <corsika/framework/core/Logging.hpp> +#include <corsika/framework/geometry/FourVector.hpp> #include <corsika/media/Environment.hpp> @@ -96,7 +97,7 @@ namespace corsika { /** * set the nodes for all particles on the stack according to their numerical - * position + * position. */ void setNodes(); @@ -127,8 +128,9 @@ namespace corsika { void step(particle_type& vParticle); ProcessReturn decay(stack_view_type& view, InverseTimeType initial_inv_decay_time); - ProcessReturn interaction(stack_view_type& view, - InverseGrammageType initial_inv_int_length); + ProcessReturn interaction(stack_view_type& view, FourMomentum const& projectileP4, + NuclearComposition const& composition, + CrossSectionType const initial_cross_section); void setEventType(stack_view_type& view, history::EventType); // data members diff --git a/corsika/framework/core/ParticleProperties.hpp b/corsika/framework/core/ParticleProperties.hpp index 8b9af126837c5d9c54942cb34b00c898283acd88..b26fdb26d4b9993f345cb807e7a92a8868aff11c 100644 --- a/corsika/framework/core/ParticleProperties.hpp +++ b/corsika/framework/core/ParticleProperties.hpp @@ -138,6 +138,13 @@ namespace corsika { */ HEPMassType constexpr get_nucleus_mass(Code const code); + /** + * @brief Calculates the mass of nucleus. + * + * @return HEPMassType the mass of (A,Z) nucleus, disregarding binding energy. + */ + HEPMassType constexpr get_nucleus_mass(unsigned int const A, unsigned int const Z); + /** * @brief Get the nucleus name. * diff --git a/corsika/framework/geometry/FourVector.hpp b/corsika/framework/geometry/FourVector.hpp index 0b87accec038a6d5d796ea91318d86fbbe004317..0a23f4e94f2d34ff892937040aea7a51a5eaccf5 100644 --- a/corsika/framework/geometry/FourVector.hpp +++ b/corsika/framework/geometry/FourVector.hpp @@ -9,36 +9,42 @@ #pragma once #include <corsika/framework/core/PhysicalUnits.hpp> -#include <corsika/framework/geometry/Vector.hpp> +#include <corsika/framework/core/PhysicalGeometry.hpp> #include <type_traits> +/** + * @file FourVector.hpp + * @author Ralf Ulrich + * @brief General FourVector object. + * @date 2021-10-16 + */ + namespace corsika { /** - Description of physical four-vectors - - FourVector fully supports units, e.g. E in [GeV/c] and p in [GeV], - or also t in [s] and r in [m], etc. - - However, for HEP applications it is also possible to use E and p - both in [GeV]. - - Thus, the input units of time-like and space-like coordinates - must either be idential (e.g. GeV) or scaled by "c" as in - [E/c]=[p]. - - - The FourVector can return its squared-norm \ref getNormSqr and its - norm \ref getNorm, whereas norm is sqrt(abs(norm-squared)). The - physical units are always calculated and returned properly. - - FourVector can also return if it is TimeLike, SpaceLike or PhotonLike. - - When a FourVector is initialized with a lvalue references, - e.g. as `FourVector<TimeType&, Vector<length_d>&>`, references - are also used as internal data types, which should lead to - complete disappearance of the FourVector class during - optimization. + * Description of physical four-vectors + * + * FourVector fully supports units, e.g. E in [GeV/c] and p in [GeV], + * or also t in [s] and r in [m], etc. + * + * However, for HEP applications it is also possible to use E and p + * both in [GeV]. + * + * Thus, the input units of time-like and space-like coordinates + * must either be idential (e.g. GeV) or scaled by "c" as in + * [E/c]=[p]. + * + * The FourVector can return its squared-norm \ref getNormSqr and its + * norm \ref getNorm, whereas norm is sqrt(abs(norm-squared)). The + * physical units are always calculated and returned properly. + * + * FourVector can also return if it is TimeLike, SpaceLike or PhotonLike. + * + * When a FourVector is initialized with a lvalue references, + * e.g. as `FourVector<TimeType&, Vector<length_d>&>`, references + * are also used as internal data types, which should lead to + * complete disappearance of the FourVector class during + * optimization. */ template <typename TTimeType, typename TSpaceVecType> @@ -73,13 +79,11 @@ namespace corsika { , spaceLike_(eS) {} /** - * * @return timeLike_ */ TTimeType getTimeLikeComponent() const; /** - * * @return spaceLike_ */ TSpaceVecType& getSpaceLikeComponents(); @@ -124,12 +128,12 @@ namespace corsika { FourVector& operator/(double const); /** - Scalar product of two FourVectors - - Note that the product between two 4-vectors assumes that you use - the same "c" convention for both. Only the LHS vector is checked - for this. You cannot mix different conventions due to - unit-checking. + * Scalar product of two FourVectors. + * + * Note that the product between two 4-vectors assumes that you use + * the same "c" convention for both. Only the LHS vector is checked + * for this. You cannot mix different conventions due to + * unit-checking. */ norm_type operator*(FourVector const& b); @@ -152,7 +156,7 @@ namespace corsika { * value-copies. * @{ * - **/ + */ friend FourVector<time_type, space_vec_type> operator+(FourVector const& a, FourVector const& b) { return FourVector<time_type, space_vec_type>(a.timeLike_ + b.timeLike_, @@ -179,20 +183,25 @@ namespace corsika { private: /** - This function is there to automatically remove the eventual - extra factor of "c" for the time-like quantity. + * This function is there to automatically remove the eventual + * extra factor of "c" for the time-like quantity. */ norm_square_type getTimeSquared() const; }; /** * streaming operator - **/ + */ template <typename TTimeType, typename TSpaceVecType> std::ostream& operator<<(std::ostream& os, corsika::FourVector<TTimeType, TSpaceVecType> const& qv); + /** + * @typedef FourMomentum A FourVector with HEPEnergyType and MomentumVector. + */ + typedef FourVector<HEPEnergyType, MomentumVector> FourMomentum; + } // namespace corsika #include <corsika/detail/framework/geometry/FourVector.inl> diff --git a/corsika/framework/process/BaseProcess.hpp b/corsika/framework/process/BaseProcess.hpp index 23e8667b20c9b92c9200135fcfe1b107c6e6c386..a871a065e4e2fe8d810bbed469ca3a50a7cf4c21 100644 --- a/corsika/framework/process/BaseProcess.hpp +++ b/corsika/framework/process/BaseProcess.hpp @@ -41,8 +41,8 @@ namespace corsika { /** @name getRef Return reference to underlying type @{ */ - TDerived& ref() { return static_cast<TDerived&>(*this); } - const TDerived& ref() const { return static_cast<const TDerived&>(*this); } + TDerived& getRef() { return static_cast<TDerived&>(*this); } + const TDerived& getRef() const { return static_cast<const TDerived&>(*this); } //! @} public: diff --git a/corsika/framework/process/DecayProcess.hpp b/corsika/framework/process/DecayProcess.hpp index 6fa5a754cdf08059062fb6348f4a20ccc2e57d34..a104343922949c58e6efd5f3c703c0ad23227fee 100644 --- a/corsika/framework/process/DecayProcess.hpp +++ b/corsika/framework/process/DecayProcess.hpp @@ -50,7 +50,7 @@ namespace corsika { template <typename TDerived> struct DecayProcess : BaseProcess<TDerived> { public: - using BaseProcess<TDerived>::ref; + using BaseProcess<TDerived>::getRef; template <typename TParticle> InverseTimeType getInverseLifetime(TParticle const& particle) { @@ -61,7 +61,7 @@ namespace corsika { "getInteractionLength(TParticle const&)\" required for " "InteractionProcess<TDerived>. "); - return 1. / ref().getLifetime(particle); + return 1. / getRef().getLifetime(particle); } }; diff --git a/corsika/framework/process/InteractionCounter.hpp b/corsika/framework/process/InteractionCounter.hpp index e38722049eaa0b00c62a4c231000821a4960120f..3b2bb182706871023119770b05fbd7b79a6d2fd2 100644 --- a/corsika/framework/process/InteractionCounter.hpp +++ b/corsika/framework/process/InteractionCounter.hpp @@ -10,24 +10,25 @@ #include <corsika/framework/process/InteractionHistogram.hpp> #include <corsika/framework/process/InteractionProcess.hpp> +#include <corsika/framework/geometry/FourVector.hpp> namespace corsika { - /*! - @ingroup Processes - @{ - + /** + * @ingroup Processes + * @{ + * * Wrapper around an InteractionProcess that fills histograms of the number * of calls to `doInteraction()` binned in projectile energy (both in - * lab and center-of-mass frame) and species + * lab and center-of-mass frame) and species. * - * Use by wrapping a normal InteractionProcess + * Use by wrapping a normal InteractionProcess: * @code{.cpp} * InteractionProcess collision1; * InteractionClounter<collision1> counted_collision1; * @endcode - * */ + template <class TCountedProcess> class InteractionCounter : public InteractionProcess<InteractionCounter<TCountedProcess>> { @@ -35,17 +36,24 @@ namespace corsika { public: InteractionCounter(TCountedProcess& process); - //! wrapper around internall process doInteraction + /** + * Wrapper around internal process doInteraction. + */ template <typename TSecondaryView> - void doInteraction(TSecondaryView& view); + void doInteraction(TSecondaryView& view, Code const, Code const, FourMomentum const&, + FourMomentum const&); - ///! returns internal process getInteractionLength - template <typename TParticle> - GrammageType getInteractionLength(TParticle const& particle) const; + /** + * Wrapper around internal process getCrossSection. + */ + CrossSectionType getCrossSection(Code const, Code const, FourMomentum const&, + FourMomentum const&) const; - /** returns the filles histograms - @return InteractionHistogram, which contains the histogram data - */ + /** + * returns the filles histograms. + * + * @return InteractionHistogram, which contains the histogram data + */ InteractionHistogram const& getHistogram() const; private: diff --git a/corsika/framework/process/InteractionProcess.hpp b/corsika/framework/process/InteractionProcess.hpp index c446e6f90ba8c8f17b54932e3c3226f8caa8b236..1736038281869041d62242937f0051c5bdceb990 100644 --- a/corsika/framework/process/InteractionProcess.hpp +++ b/corsika/framework/process/InteractionProcess.hpp @@ -10,67 +10,54 @@ #include <corsika/framework/process/BaseProcess.hpp> #include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/media/NuclearComposition.hpp> #include <corsika/detail/framework/process/InteractionProcess.hpp> // for extra traits, method/interface checking namespace corsika { /** - @ingroup Processes - @{ - - Process describing the interaction of particles - - Create a new InteractionProcess, e.g. for XYModel, via - @code - class XYModel : public InteractionProcess<XYModel> {}; - @endcode - - and provide the two necessary interface methods - @code - template <typename TSecondaryView> - void XYModel::doInteraction(TSecondaryView&); - - template <typename TParticle> - GrammageType XYModel::getInteractionLength(TParticle const&) - @endcode - - Where, of course, SecondaryView and Particle are the valid - classes to access particles on the Stack. In user code, those two methods do - not need to be templated, they could use the types - e.g. corsika::setup::Stack::particle_type -- but by the cost of - loosing all flexibility otherwise provided. - - SecondaryView allows to retrieve the properties of the projectile - particles, AND to store new particles (secondaries) which then - subsequently can be processes by SecondariesProcess. This is how - the output of interactions can be studied right away. - + * @ingroup Processes + * @{ + * + * Process describing the interaction of particles. + * + * Create a new InteractionProcess, e.g. for XYModel, via: + * @code + * class XYModel : public InteractionProcess<XYModel> {}; + * @endcode + * + * and provide the two necessary interface methods: + * @code + * template <typename TSecondaryView> + * void XYModel::doInteraction(TSecondaryView&); + * + * template <typename TParticle> + * GrammageType XYModel::getInteractionLength(TParticle const&) + * @endcode + * + * Where, of course, SecondaryView and Particle are the valid + * classes to access particles on the Stack. In user code, those two methods do + * not need to be templated, they could use the types + * e.g. corsika::setup::Stack::particle_type -- but by the cost of + * loosing all flexibility otherwise provided. + * + * SecondaryView allows to retrieve the properties of the projectile + * particles, AND to store new particles (secondaries) which then + * subsequently can be processes by SecondariesProcess. This is how + * the output of interactions can be studied right away. */ - template <typename TDerived> - class InteractionProcess : public BaseProcess<TDerived> { + template <typename TModel> + class InteractionProcess : public BaseProcess<TModel> { public: - using BaseProcess<TDerived>::ref; - - template <typename TParticle> - InverseGrammageType getInverseInteractionLength(TParticle const& particle) { - - // interface checking on TProcess1 - static_assert( - has_method_getInteractionLength_v<TDerived, GrammageType, TParticle const&>, - "TDerived has no method with correct signature \"GrammageType " - "getInteractionLength(TParticle const&)\" required for " - "InteractionProcess<TDerived>. "); - - return 1. / ref().getInteractionLength(particle); - } + using BaseProcess<TModel>::getRef; }; /** - * ProcessTraits specialization to flag InteractionProcess objects - **/ + * ProcessTraits specialization to flag InteractionProcess objects. + */ template <typename TProcess> struct is_interaction_process< TProcess, std::enable_if_t< diff --git a/corsika/framework/process/ProcessSequence.hpp b/corsika/framework/process/ProcessSequence.hpp index 407f403495c7c4d281880a897366f3b76b22a123..e6d3600b8cff72e305a1b57f64a526a39772341f 100644 --- a/corsika/framework/process/ProcessSequence.hpp +++ b/corsika/framework/process/ProcessSequence.hpp @@ -25,14 +25,21 @@ #include <corsika/framework/process/SecondariesProcess.hpp> #include <corsika/framework/process/StackProcess.hpp> #include <corsika/framework/process/NullModel.hpp> + #include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/framework/core/ParticleProperties.hpp> + +#include <corsika/framework/geometry/FourVector.hpp> namespace corsika { + class COMBoost; // fwd-decl + class NuclearComposition; // fwd-decl + /** - count_processes traits specialization to increase process count by - getNumberOfProcesses(). This is used to statically count processes in the sequence - **/ + * count_processes traits specialization to increase process count by + * getNumberOfProcesses(). This is used to statically count processes in the sequence. + */ template <typename TProcess, int N> struct count_processes< TProcess, N, @@ -43,110 +50,109 @@ namespace corsika { }; /** - @defgroup Processes Physics Processes and Modules - - Physics processes in CORSIKA 8 are clustered in ProcessSequence and - SwitchProcessSequence containers. The former is a mere (ordered) collection, while - the latter has the option to switch between two alternative ProcessSequences. - - Depending on the type of data to act on and on the allowed actions of processes there - are several interface options: - - InteractionProcess - - DecayProcess - - ContinuousProcess - - StackProcess - - SecondariesProcess - - BoundaryCrossingProcess - - And all processes (including ProcessSequence and SwitchProcessSequence) are derived - from BaseProcess. - - Processes of any type (e.g. p1, p2, p3,...) can be assembled into a ProcessSequence - using the `make_sequence` factory function. - - @code{.cpp} - auto sequence1 = make_sequence(p1, p2, p3); - auto sequence2 = make_sequence(p4, p5, p6, p7); - auto sequence3 = make_sequence(sequence1, sequemce2, p8, p9); - @endcode - - Note, if the order of processes - matters, the order of occurence - in the ProcessSequence determines - the executiion order. - - SecondariesProcess alyways act on - new secondaries produced (i.e. in - InteractionProcess and - DecayProcess) in the scope of - their ProcessSequence. For - example if i1 and i2 are - InteractionProcesses and s1 is a - SecondariesProcess, then - - @code{.cpp} - auto sequence = make_sequence(i1, make_sequence(i2, s1)) - @endcode - - will result in s1 acting only on - the particles produced by i2 and - not by i1. This can be very - useful, e.g. to fine tune thinning. - - A special type of ProcessSequence - is SwitchProcessSequence, which - has two branches and a functor - that can select between these two - branches. - - @code{.cpp} - auto sequence = make_switch(sequence1, sequence2, selector); - @endcode - - where the only requirement to - `selector` is that it - provides a `SwitchResult operator()(Particle const& particle) const` method. Thus, - based on the dynamic properties - of `particle` the functor - can make its decision. This is - clearly important for switching - between low-energy and - high-energy models, but not - limited to this. The selection - can even be done with a lambda - function. - - - @class ProcessSequence - @ingroup Processes - - Definition of a static process list/sequence - - A compile time static list of processes. The compiler will - generate a new type based on template logic containing all the - elements provided by the user. - - TProcess1 and TProcess2 must both be derived from BaseProcess, - and are both references if possible (lvalue), otherwise (rvalue) - they are just classes. This allows us to handle both, rvalue as - well as lvalue Processes in the ProcessSequence. - - (For your potential interest, - the static version of the - ProcessSequence and all Process - types are based on the CRTP C++ - design pattern) - - Template parameters: - @tparam TProcess1 is of type BaseProcess, either a dedicatd process, or a - ProcessSequence - @tparam TProcess2 is of type BaseProcess, either a dedicatd process, or a - ProcessSequence - @tparam IndexFirstProcess to count and index each Process in the entire - process-chain. The offset is the starting value for this ProcessSequence - @tparam IndexOfProcess1 index of TProcess1 (counting of Process) - @tparam IndexOfProcess2 index of TProcess2 (counting of Process) - **/ + * @defgroup Processes Physics Processes and Modules + * + * Physics processes in CORSIKA 8 are clustered in ProcessSequence and + * SwitchProcessSequence containers. The former is a mere (ordered) collection, while + * the latter has the option to switch between two alternative ProcessSequences. + * + * Depending on the type of data to act on and on the allowed actions of + * processes there are several interface options: + * - InteractionProcess + * - DecayProcess + * - ContinuousProcess + * - StackProcess + * - SecondariesProcess + * - BoundaryCrossingProcess + * + * And all processes (including ProcessSequence and SwitchProcessSequence) are derived + * from BaseProcess. + * + * Processes of any type (e.g. p1, p2, p3,...) can be assembled into a ProcessSequence + * using the `make_sequence` factory function. + * + * @code{.cpp} + * auto sequence1 = make_sequence(p1, p2, p3); + * auto sequence2 = make_sequence(p4, p5, p6, p7); + * auto sequence3 = make_sequence(sequence1, sequemce2, p8, p9); + * @endcode + * + * Note, if the order of processes + * matters, the order of occurence + * in the ProcessSequence determines + * the executiion order. + * + * SecondariesProcess alyways act on + * new secondaries produced (i.e. in + * InteractionProcess and + * DecayProcess) in the scope of + * their ProcessSequence. For + * example if i1 and i2 are + * InteractionProcesses and s1 is a + * SecondariesProcess, then: + * + * @code{.cpp} + * auto sequence = make_sequence(i1, make_sequence(i2, s1)) + * @endcode + * + * will result in s1 acting only on + * the particles produced by i2 and + * not by i1. This can be very + * useful, e.g. to fine tune thinning. + * + * A special type of ProcessSequence + * is SwitchProcessSequence, which + * has two branches and a functor + * that can select between these two + * branches. + * + * @code{.cpp} + * auto sequence = make_switch(sequence1, sequence2, selector); + * @endcode + * + * where the only requirement to + * `selector` is that it + * provides a `SwitchResult operator()(Particle const& particle) const` method. Thus, + * based on the dynamic properties + * of `particle` the functor + * can make its decision. This is + * clearly important for switching + * between low-energy and + * high-energy models, but not + * limited to this. The selection + * can even be done with a lambda + * function. + * + * @class ProcessSequence + * @ingroup Processes + * + * Definition of a static process list/sequence. + * + * A compile time static list of processes. The compiler will + * generate a new type based on template logic containing all the + * elements provided by the user. + * + * TProcess1 and TProcess2 must both be derived from BaseProcess, + * and are both references if possible (lvalue), otherwise (rvalue) + * they are just classes. This allows us to handle both, rvalue as + * well as lvalue Processes in the ProcessSequence. + * + * (For your potential interest, + * the static version of the + * ProcessSequence and all Process + * types are based on the CRTP C++ + * design pattern). + * + * Template parameters: + * @tparam TProcess1 is of type BaseProcess, either a dedicatd process, or a + * ProcessSequence. + * @tparam TProcess2 is of type BaseProcess, either a dedicatd process, or a + * ProcessSequence. + * @tparam IndexFirstProcess to count and index each Process in the entire + * process-chain. The offset is the starting value for this ProcessSequence. + * @tparam IndexOfProcess1 index of TProcess1 (counting of Process). + * @tparam IndexOfProcess2 index of TProcess2 (counting of Process). + */ template <typename TProcess1, typename TProcess2 = NullModel, int ProcessIndexOffset = 0, @@ -171,17 +177,26 @@ namespace corsika { static bool const is_process_sequence = true; /** - Only valid user constructor will create fully initialized object - - ProcessSequence supports and encourages move semantics. You can - use object, l-value references or r-value references to - construct sequences. - - @param in_A BaseProcess or switch/process list - @param in_B BaseProcess or switch/process list - **/ + * Only valid user constructor will create fully initialized object. + * + * ProcessSequence supports and encourages move semantics. You can + * use object, l-value references or r-value references to + * construct sequences. + * + * @param in_A BaseProcess or switch/process list. + * @param in_B BaseProcess or switch/process list. + */ ProcessSequence(TProcess1 in_A, TProcess2 in_B); + /** + * List of all BoundaryProcess. + * + * @tparam TParticle + * @param particle The particle. + * @param from Volume the particle is exiting. + * @param to Volume the particle is entering. + * @return ProcessReturn + */ template <typename TParticle> ProcessReturn doBoundaryCrossing(TParticle& particle, typename TParticle::node_type const& from, @@ -191,21 +206,30 @@ namespace corsika { ProcessReturn doContinuous(TParticle& particle, TTrack& vT, ContinuousProcessIndex const limitID); + /** + * Process all secondaries in TSecondaries. + * + * The seondaries produced by other processes and accessible via TSecondaries + * are processed by all SecondariesProcesse via a call here. + * + * @tparam TSecondaries + * @param vS + */ template <typename TSecondaries> void doSecondaries(TSecondaries& vS); /** - The processes of type StackProcess do have an internal counter, - so they can be exectuted only each N steps. Often these are - "maintenacne processes" that do not need to run after each - single step of the simulations. In the CheckStep function it is - tested if either A_ or B_ are StackProcess and if they are due - for execution. + * The processes of type StackProcess do have an internal counter, + * so they can be exectuted only each N steps. Often these are + * "maintenacne processes" that do not need to run after each + * single step of the simulations. In the CheckStep function it is + * tested if either A_ or B_ are StackProcess and if they are due + * for execution. */ bool checkStep(); /** - Execute the StackProcess-es in the ProcessSequence + * Execute the StackProcess-es in the ProcessSequence. */ template <typename TStack> void doStack(TStack& stack); @@ -223,49 +247,80 @@ namespace corsika { /** * Calculate the maximum allowed length of the next tracking step, based on all - * ContinuousProcess-es + * ContinuousProcess-es. * * The maximum allowed step length is the minimum of the allowed track lenght over all * ContinuousProcess-es in the ProcessSequence. * * @return ContinuousProcessStepLength which contains the step length itself in * LengthType, and a unique identifier of the related ContinuousProcess. - **/ + */ template <typename TParticle, typename TTrack> - ContinuousProcessStepLength getMaxStepLength(TParticle& particle, TTrack& vTrack); - - template <typename TParticle> - GrammageType getInteractionLength(TParticle&& particle) { - return 1. / getInverseInteractionLength(particle); - } + ContinuousProcessStepLength getMaxStepLength(TParticle&& particle, TTrack&& vTrack); + /** + * @brief Calculates the cross section of a projectile with a target. + * + * @tparam TParticle + * @param projectile + * @param targetId + * @param targetP4 + * @return CrossSectionType + */ template <typename TParticle> - InverseGrammageType getInverseInteractionLength(TParticle&& particle); - - template <typename TSecondaryView> - ProcessReturn selectInteraction( - TSecondaryView& view, [[maybe_unused]] InverseGrammageType lambda_inv_select, - [[maybe_unused]] InverseGrammageType lambda_inv_sum = - InverseGrammageType::zero()); + CrossSectionType getCrossSection(TParticle const& projectile, Code const targetId, + FourMomentum const& targetP4) const; template <typename TParticle> TimeType getLifetime(TParticle& particle) { return 1. / getInverseLifetime(particle); } + /** + * Selects one concrete InteractionProcess and samples a target nucleus from + * the material. + * + * The selectInteraction method statically loops over all active InteractionProcess + * and calculates the material-weighted cross section for all of them. In an iterative + * way those cross sections are summed up. The random number cx_select, uniformely + * drawn from the cross section before energy losses, is used to discriminate the + * selected sub-process here. If the cross section after the step smaller than it was + * before, there is a non-zero probability that the particle survives and no + * interaction takes place. This method becomes imprecise when cross section rise with + * falling energies. + * + * If a sub-process was selected, the target nucleus is selected from the material + * (weighted with cross section). The interaction is then executed. + * + * @tparam TSecondaryView Object type as storage for new secondary particles. + * @tparam TRNG Object type to produce random numbers. + * @param view Object to store new secondary particles. + * @param projectileP4 The four momentum of the projectile. + * @param composition The environment/material composition. + * @param rng Random number object. + * @param cx_select Drawn random numer, uniform between [0, cx_initial] + * @param cx_sum For interal use, to sum up cross section contributions. + * @return ProcessReturn + */ + template <typename TSecondaryView, typename TRNG> + inline ProcessReturn selectInteraction( + TSecondaryView&& view, FourMomentum const& projectileP4, + NuclearComposition const& composition, TRNG&& rng, + CrossSectionType const cx_select, + CrossSectionType cx_sum = CrossSectionType::zero()); + template <typename TParticle> InverseTimeType getInverseLifetime(TParticle&& particle); // select decay process template <typename TSecondaryView> - ProcessReturn selectDecay( - TSecondaryView& view, [[maybe_unused]] InverseTimeType decay_inv_select, - [[maybe_unused]] InverseTimeType decay_inv_sum = InverseTimeType::zero()); + ProcessReturn selectDecay(TSecondaryView&& view, InverseTimeType decay_inv_select, + InverseTimeType decay_inv_sum = InverseTimeType::zero()); /** * static counter to uniquely index (count) all ContinuousProcess in switch sequence. - **/ + */ static unsigned int constexpr getNumberOfProcesses() { return numberOfProcesses_; } #ifdef CORSIKA_UNIT_TESTING @@ -274,40 +329,40 @@ namespace corsika { #endif private: - TProcess1 A_; /// process/list A, this is a reference, if possible - TProcess2 B_; /// process/list B, this is a reference, if possible + TProcess1 A_; //! process/list A, this is a reference, if possible + TProcess2 B_; //! process/list B, this is a reference, if possible static unsigned int constexpr numberOfProcesses_ = IndexOfProcess1; // static counter }; /** - @fn make_sequence - @ingroup Processes - - Factory function to create a ProcessSequence - - to construct ProcessSequences in a flexible and dynamic way the - `sequence` factory functions are provided - - Any objects of type - - BaseProcess - - ContinuousProcess and - - InteractionProcess/DecayProcess - - StackProcess - - SecondariesProcess - - BoundaryCrossingProcess - - can be assembled into a ProcessSequence, all - combinatorics are allowed. - - The sequence function checks that all its arguments are all of - types derived from BaseProcess. Also the ProcessSequence itself - is derived from type BaseProcess - - @tparam TProcesses parameter pack with objects of type BaseProcess - @tparam TProcess1 another BaseProcess - @param vA needs to derive from BaseProcess - @param vB paramter-pack, needs to derive BaseProcess + * @fn make_sequence + * @ingroup Processes + * + * Factory function to create a ProcessSequence. + * + * to construct ProcessSequences in a flexible and dynamic way the + * `sequence` factory functions are provided. + * + * Any objects of type + * - BaseProcess + * - ContinuousProcess and + * - InteractionProcess/DecayProcess + * - StackProcess + * - SecondariesProcess + * - BoundaryCrossingProcess + * + * can be assembled into a ProcessSequence, all + * combinatorics are allowed. + * + * The sequence function checks that all its arguments are all of + * types derived from BaseProcess. Also the ProcessSequence itself + * is derived from type BaseProcess. + * + * @tparam TProcesses parameter pack with objects of type BaseProcess. + * @tparam TProcess1 another BaseProcess. + * @param vA needs to derive from BaseProcess. + * @param vB paramter-pack, needs to derive BaseProcess. */ template <typename... TProcesses, typename TProcess1> ProcessSequence<TProcess1, decltype(make_sequence(std::declval<TProcesses>()...))> @@ -318,34 +373,34 @@ namespace corsika { } /** - @fn make_sequence - @ingroup Processes - - Factory function to create ProcessSequence - - specialization for two input objects (no paramter pack in vB). - - @tparam TProcess1 another BaseProcess - @tparam TProcess2 another BaseProcess - @param vA needs to derive from BaseProcess - @param vB needs to derive BaseProcess - */ + * @fn make_sequence + * @ingroup Processes + * + * Factory function to create ProcessSequence. + * + * specialization for two input objects (no paramter pack in vB). + * + * @tparam TProcess1 another BaseProcess + * @tparam TProcess2 another BaseProcess + * @param vA needs to derive from BaseProcess + * @param vB needs to derive BaseProcess + */ template <typename TProcess1, typename TProcess2> ProcessSequence<TProcess1, TProcess2> make_sequence(TProcess1&& vA, TProcess2&& vB) { return ProcessSequence<TProcess1, TProcess2>(vA, vB); } /** - @fn make_sequence - @ingroup Processes - - Factory function to create ProcessSequence from a single BaseProcess - - also allow a single Process in ProcessSequence, accompany by - `NullModel` - - @tparam TProcess1 another BaseProcess - @param vA needs to derive from BaseProcess + * @fn make_sequence + * @ingroup Processes + * + * Factory function to create ProcessSequence from a single BaseProcess. + * + * also allow a single Process in ProcessSequence, accompany by + * `NullModel`. + * + * @tparam TProcess1 another BaseProcess + * @param vA needs to derive from BaseProcess */ template <typename TProcess> ProcessSequence<TProcess, NullModel> make_sequence(TProcess&& vA) { diff --git a/corsika/framework/process/SwitchProcessSequence.hpp b/corsika/framework/process/SwitchProcessSequence.hpp index 09edeea43e551f7f37b51e8b105c78cc92ba6f6c..2b619036994b57ba62586980589cbe1888f5d8cc 100644 --- a/corsika/framework/process/SwitchProcessSequence.hpp +++ b/corsika/framework/process/SwitchProcessSequence.hpp @@ -146,18 +146,15 @@ namespace corsika { ContinuousProcessStepLength getMaxStepLength(TParticle& particle, TTrack& vTrack); template <typename TParticle> - GrammageType getInteractionLength(TParticle&& particle) { - return 1. / getInverseInteractionLength(particle); - } - - template <typename TParticle> - InverseGrammageType getInverseInteractionLength(TParticle&& particle); - - template <typename TSecondaryView> - ProcessReturn selectInteraction( - TSecondaryView& view, [[maybe_unused]] InverseGrammageType lambda_inv_select, - [[maybe_unused]] InverseGrammageType lambda_inv_sum = - InverseGrammageType::zero()); + CrossSectionType getCrossSection(TParticle const& projectile, Code const targetId, + FourMomentum const& targetP4) const; + + template <typename TSecondaryView, typename TRNG> + ProcessReturn selectInteraction(TSecondaryView& view, + FourMomentum const& projectileP4, + NuclearComposition const& composition, TRNG& rng, + CrossSectionType const cx_select, + CrossSectionType cx_sum = CrossSectionType::zero()); template <typename TParticle> TimeType getLifetime(TParticle&& particle) { diff --git a/corsika/framework/utility/COMBoost.hpp b/corsika/framework/utility/COMBoost.hpp index eac8dde53f3eadc461e26c559e8b8ed36463b647..98be9fc070a90c2918975de8d0debbe15505ed3c 100644 --- a/corsika/framework/utility/COMBoost.hpp +++ b/corsika/framework/utility/COMBoost.hpp @@ -19,56 +19,71 @@ namespace corsika { /** - @defgroup Utilities - - Collection of classes and methods to perform recurring tasks. - **/ + * @defgroup Utilities + * + * Collection of classes and methods to perform recurring tasks. + */ /** - @class COMBoost - @ingroup Utilities - - This utility class handles Lorentz boost between different - referenence frames, using FourVector. - - The class is initialized with projectile and optionally target - energy/momentum data. During initialization, a rotation matrix is - calculated to represent the projectile movement (and thus the - boost) along the z-axis. Also the inverse of this rotation is - calculated. The Lorentz boost matrix and its inverse are - determined as 2x2 matrices considering the energy and - pz-momentum. - - Different constructors are offered with different specialization - for the cases of collisions (projectile-target) or just decays - (projectile only). + * @class COMBoost + * @ingroup Utilities + * + * This utility class handles Lorentz boost (in one spatial direction) + * between different referenence frames, using FourVector. + * + * The class is initialized with projectile and optionally target + * energy/momentum data. During initialization, a rotation matrix is + * calculated to represent the projectile movement (and thus the + * boost) along the z-axis. Also the inverse of this rotation is + * calculated. The Lorentz boost matrix and its inverse are + * determined as 2x2 matrices considering the energy and + * pz-momentum. + * + * Different constructors are offered with different specialization + * for the cases of collisions (projectile-target) or just decays + * (projectile only). */ class COMBoost { public: - //! construct a COMBoost given four-vector of projectile and mass of target (target at - //! rest) - COMBoost(FourVector<HEPEnergyType, MomentumVector> const& Pprojectile, - HEPEnergyType const massTarget); - - //! construct a COMBoost to boost into the rest frame given a 3-momentum and mass - COMBoost(MomentumVector const& momentum, HEPEnergyType mass); + /** + * Construct a COMBoost given four-vector of projectile and mass of target (target at + * rest). + * + * The FourMomentum and mass define the lab system. + */ + COMBoost(FourMomentum const& P4projectile, HEPEnergyType const massTarget); + + /** + * Construct a COMBoost to boost into the rest frame given a 3-momentum and mass. + */ + COMBoost(MomentumVector const& momentum, HEPEnergyType const mass); + + /** + * Construct a COMBoost given two four-vectors of projectile target. + * + * The tow FourMomentum can define an arbitrary system. + */ + COMBoost(FourMomentum const& P4projectile, FourMomentum const& P4target); //! transforms a 4-momentum from lab frame to the center-of-mass frame template <typename FourVector> - FourVector toCoM(FourVector const& p) const; + FourVector toCoM(FourVector const& p4) const; //! transforms a 4-momentum from the center-of-mass frame back to lab frame template <typename FourVector> - FourVector fromCoM(FourVector const& p) const; + FourVector fromCoM(FourVector const& p4) const; - //! returns the rotated coordinate system + //! returns the rotated coordinate system: +z is projectile direction CoordinateSystemPtr getRotatedCS() const; + //! returns the original coordinate system of the projectile (lab) + CoordinateSystemPtr getOriginalCS() const; + protected: //! internal method - void setBoost(double coshEta, double sinhEta); + void setBoost(double const coshEta, double const sinhEta); private: Eigen::Matrix2d boost_; diff --git a/corsika/media/IMediumPropertyModel.hpp b/corsika/media/IMediumPropertyModel.hpp index 860fa22da0e5d8d09ea4b9dbb9aeb8219cfc0492..94e80fb51a4f24b0dc7deb8f770d1a457f4c9914 100644 --- a/corsika/media/IMediumPropertyModel.hpp +++ b/corsika/media/IMediumPropertyModel.hpp @@ -16,10 +16,9 @@ namespace corsika { /** - * An interface for type of media, needed e.g. to determine energy losses + * An interface for type of media, needed e.g. to determine energy losses. * * This is the base interface for media types. - * */ template <typename TModel> class IMediumPropertyModel : public TModel { diff --git a/corsika/media/MediumPropertyModel.hpp b/corsika/media/MediumPropertyModel.hpp index ab89a8a543a0d25da648d06e45d34adfb5cecec1..97bce6ff8fd15e60d8a81e3c33694087cb77512d 100644 --- a/corsika/media/MediumPropertyModel.hpp +++ b/corsika/media/MediumPropertyModel.hpp @@ -14,7 +14,6 @@ namespace corsika { /** * A model for the energy loss property of a medium. - * */ template <typename T> class MediumPropertyModel : public T { diff --git a/corsika/media/NuclearComposition.hpp b/corsika/media/NuclearComposition.hpp index e4ad9513a24684eed2ca1c3b9862441003f2c0fa..184853ef2cdc620c821b516c4760144d503ffc63 100644 --- a/corsika/media/NuclearComposition.hpp +++ b/corsika/media/NuclearComposition.hpp @@ -20,48 +20,69 @@ namespace corsika { - /** Describes the composition of matter - * Allowes and handles the creation of custom matter compositions - **/ + /** + * Describes the composition of matter + * Allowes and handles the creation of custom matter compositions. + */ + class NuclearComposition { public: - /** Constructor + /** + * Constructor * The constructore takes a list of elements and a list which describe the relative * amount. Booth lists need to have the same length and the sum all of fractions - * should be 1. Otherwise an exception is thrown - * @param pComponents List of particle types + * should be 1. Otherwise an exception is thrown. + * + * @param pComponents List of particle types. * @param pFractions List of fractions how much each particle contributes. The sum - * needs to add up to 1 - **/ + * needs to add up to 1. + */ NuclearComposition(std::vector<Code> const& pComponents, - std::vector<float> const& pFractions); + std::vector<double> const& pFractions); + + /** + * Returns a vector of the same length as elements in the material with the weighted + * return of "func". The typical default application is for cross section weighted + * with fraction in the material. + * + * @tparam TFunction Type of functions for the weights. The type should be + * Code -> CrossSectionType. + * @param func Functions for reweighting specific elements. + * @retval returns the vector with weighted return types of func. + */ + template <typename TFunction> + auto getWeighted(TFunction const& func) const; - /** Sum all all relative composition weighted by func(element) + /** + * Sum all all relative composition weighted by func(element) * This function sums all relative compositions given during this classes - *construction. Each entry is weighted by the user defined function func given to this - *function. + * construction. Each entry is weighted by the user defined function func given to + * this function. + * * @tparam TFunction Type of functions for the weights. The type should be - * Code -> float - * @param func Functions for reweighting specific elements - * @retval returns the weighted sum with the type defined by the return type of func - **/ + * Code -> double. + * @param func Functions for reweighting specific elements. + * @retval returns the weighted sum with the type defined by the return type of func. + */ template <typename TFunction> - auto getWeightedSum(TFunction const& func) const; + auto getWeightedSum(TFunction const& func) const + -> decltype(func(std::declval<Code>())); - /** Number of elements in the composition array - * @retval returns the number of elements in the composition array - **/ + /** + * Number of elements in the composition array + * @retval returns the number of elements in the composition array. + */ size_t getSize() const; //! Returns a const reference to the fraction - std::vector<float> const& getFractions() const; + std::vector<double> const& getFractions() const; //! Returns a const reference to the fraction std::vector<Code> const& getComponents() const; double const getAverageMassNumber() const; template <class TRNG> Code sampleTarget(std::vector<CrossSectionType> const& sigma, - TRNG& randomStream) const; + TRNG&& randomStream) const; // Note: when this class ever modifies its internal data, the hash // must be updated, too! @@ -73,8 +94,8 @@ namespace corsika { private: void updateHash(); - std::vector<float> const numberFractions_; //!< relative fractions of number density - std::vector<Code> const components_; //!< particle codes of consitutents + std::vector<double> const numberFractions_; //!< relative fractions of number density + std::vector<Code> const components_; //!< particle codes of consitutents double const avgMassNumber_; diff --git a/corsika/modules/Epos.hpp b/corsika/modules/Epos.hpp index c25be9b14b615e797bb821d06d2bc27b7b23353a..b4aa9086cf563e35b0ffb23d4aa9c85f6375cf60 100644 --- a/corsika/modules/Epos.hpp +++ b/corsika/modules/Epos.hpp @@ -9,4 +9,22 @@ #pragma once #include <corsika/modules/epos/ParticleConversion.hpp> -#include <corsika/modules/epos/Interaction.hpp> +#include <corsika/modules/epos/InteractionModel.hpp> +#include <corsika/framework/process/InteractionProcess.hpp> + +/** + * @file Sibyll.hpp + * + * Includes all the parts of the EPOS model. Defines the InteractionProcess<TModel> + * classes needed for the ProcessSequence. + */ + +namespace corsika::epos { + /** + * epos::Interaction is the process for ProcessSequence. + * + * The epos::InteractionModel is wrapped as an InteractionProcess here in order + * to provide all the functions for ProcessSequence. + */ + class Interaction : public InteractionModel, public InteractionProcess<Interaction> {}; +} // namespace corsika::epos \ No newline at end of file diff --git a/corsika/modules/LongitudinalProfile.hpp b/corsika/modules/LongitudinalProfile.hpp index 1d84462b0573f5407efb9a531ce5299df2b9cd58..b5d199fb87949e5456e47b097eea65248d8d5be2 100644 --- a/corsika/modules/LongitudinalProfile.hpp +++ b/corsika/modules/LongitudinalProfile.hpp @@ -31,8 +31,7 @@ namespace corsika { * simulation into a projected grammage range and counts for * different particle species when they cross dX (default: 10g/cm2) * boundaries. - * - **/ + */ class LongitudinalProfile : public ContinuousProcess<LongitudinalProfile> { diff --git a/corsika/modules/QGSJetII.hpp b/corsika/modules/QGSJetII.hpp index 9540c4ad116471d54a5ef178124bd97b979d9758..dd7422442e06c8a5a36f94b65cf9dd0fe659ea4c 100644 --- a/corsika/modules/QGSJetII.hpp +++ b/corsika/modules/QGSJetII.hpp @@ -8,4 +8,23 @@ #pragma once -#include <corsika/modules/qgsjetII/Interaction.hpp> +#include <corsika/modules/qgsjetII/InteractionModel.hpp> + +#include <corsika/framework/process/InteractionProcess.hpp> + +/** + * @file QGSJetII.hpp + * + * Includes all the parts of the QGSJetII model. Defines the InteractionProcess<TModel> + * classes needed for the ProcessSequence. + */ + +namespace corsika::qgsjetII { + /** + * @brief qgsjetII::Interaction is the process for ProcessSequence. + * + * The qgsjetII::InteractionModel is wrapped as an InteractionProcess here in order + * to provide all the functions for ProcessSequence. + */ + class Interaction : public InteractionModel, public InteractionProcess<Interaction> {}; +} // namespace corsika::qgsjetII diff --git a/corsika/modules/Sibyll.hpp b/corsika/modules/Sibyll.hpp index 44bebf0ff174d4dab8234e2d17d0d67b1ff58416..8d781def6b124e179ec0ad0eb2abe9955de242dd 100644 --- a/corsika/modules/Sibyll.hpp +++ b/corsika/modules/Sibyll.hpp @@ -9,6 +9,41 @@ #pragma once #include <corsika/modules/sibyll/ParticleConversion.hpp> -#include <corsika/modules/sibyll/Interaction.hpp> +#include <corsika/modules/sibyll/InteractionModel.hpp> #include <corsika/modules/sibyll/Decay.hpp> -#include <corsika/modules/sibyll/NuclearInteraction.hpp> +#include <corsika/modules/sibyll/NuclearInteractionModel.hpp> + +#include <corsika/framework/process/InteractionProcess.hpp> + +/** + * @file Sibyll.hpp + * + * Includes all the parts of the Sibyll model. Defines the InteractionProcess<TModel> + * classes needed for the ProcessSequence. + */ + +namespace corsika::sibyll { + /** + * @brief sibyll::Interaction is the process for ProcessSequence. + * + * The sibyll::InteractionModel is wrapped as an InteractionProcess here in order + * to provide all the functions for ProcessSequence. + */ + class Interaction : public InteractionModel, public InteractionProcess<Interaction> {}; + + /** + * @brief sibyll::NuclearInteraction is the process for ProcessSequence. + * + * The sibyll::NuclearInteractionModel is wrapped as an InteractionProcess here in order + * to provide all the functions for ProcessSequence. + */ + template <class TEnvironment, class TNucleonModel> + class NuclearInteraction + : public NuclearInteractionModel<TEnvironment, TNucleonModel>, + public InteractionProcess<NuclearInteraction<TEnvironment, TNucleonModel>> { + public: + NuclearInteraction(TNucleonModel& model, TEnvironment const& env) + : NuclearInteractionModel<TEnvironment, TNucleonModel>(model, env) {} + }; + +} // namespace corsika::sibyll diff --git a/corsika/modules/epos/Interaction.hpp b/corsika/modules/epos/InteractionModel.hpp similarity index 64% rename from corsika/modules/epos/Interaction.hpp rename to corsika/modules/epos/InteractionModel.hpp index 56900ca01cdf99f41f6dc38b7cc9578bc6e7afad..0f08a919b7cc640d20351235dfc2164b155dbe50 100644 --- a/corsika/modules/epos/Interaction.hpp +++ b/corsika/modules/epos/InteractionModel.hpp @@ -10,20 +10,18 @@ #include <corsika/framework/core/ParticleProperties.hpp> #include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/framework/geometry/FourVector.hpp> #include <corsika/framework/random/RNGManager.hpp> -#include <corsika/framework/process/InteractionProcess.hpp> #include <tuple> namespace corsika::epos { - class Interaction : public InteractionProcess<Interaction> { - std::string data_path_; - unsigned int count_ = 0; - bool epos_listing_; + class InteractionModel { public: - Interaction(std::string const& dataPath = "", bool const epos_printout_on = false); - ~Interaction(); + InteractionModel(std::string const& dataPath = "", + bool const epos_printout_on = false); + ~InteractionModel(); //! returns production and elastic cross section for hadrons in epos. Inputs are: //! CorsikaId of beam particle, CorsikaId of target particle, center-of-mass energy. @@ -41,27 +39,39 @@ namespace corsika::epos { //! returns production and elastic cross section. Allowed configurations are //! hadron-nucleon, hadron-nucleus and nucleus-nucleus. Inputs are particle id's mass //! and charge numbers and total energy in the lab. - std::tuple<CrossSectionType, CrossSectionType> getCrossSectionLab( - Code const, int const, int const, Code const, int const, int const, - HEPEnergyType const) const; - - template <typename TParticle> - GrammageType getInteractionLength(TParticle const&) const; + std::tuple<CrossSectionType, CrossSectionType> getCrossSectionInelEla( + Code const projectileId, Code const targetId, FourMomentum const& projectileP4, + FourMomentum const& targetP4) const; /** - In this function EPOSLHC is called to produce one event. The - event is copied into the shower lab frame. + * Checks validity of projectile, target and energy combination. */ - template <typename TSecondaries> - void doInteraction(TSecondaries&); + bool isValid(Code const projectileId, Code const targetId, + HEPEnergyType const sqrtS) const; - bool isValidCoMEnergy(HEPEnergyType const ecm) const { - return (minEnergyCoM_ <= ecm) && (ecm <= maxEnergyCoM_); + /** + * Get the inelatic/production cross section. + * + * @param projectileId + * @param targetId + * @param projectileP4 + * @param targetP4 + * @return CrossSectionType + */ + CrossSectionType getCrossSection(Code const projectileId, Code const targetId, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) const { + return std::get<0>( + getCrossSectionInelEla(projectileId, targetId, projectileP4, targetP4)); } - //! eposlhc only accepts nuclei with X<=A<=Y as targets, or protons aka Hydrogen or - //! neutrons (p,n == nucleon) - bool isValidTarget(Code const) const; + /** + * In this function EPOSLHC is called to produce one event. The + * event is copied into the shower lab frame. + */ + template <typename TSecondaries> + void doInteraction(TSecondaries&, Code const projectileId, Code const targetId, + FourMomentum const& projectileP4, FourMomentum const& targetP4); void initialize() const; void initializeEventCoM(Code const, int const, int const, Code const, int const, @@ -73,6 +83,12 @@ namespace corsika::epos { void setParticlesStable() const; private: + inline static bool isInitialized_ = false; + + std::string data_path_; + unsigned int count_ = 0; + bool epos_listing_; + default_prng_type& RNG_ = RNGManager<>::getInstance().getRandomStream("epos"); std::shared_ptr<spdlog::logger> logger_ = get_logger("corsika_epos_Interaction"); HEPEnergyType const minEnergyCoM_ = 6 * 1e9 * electronvolt; @@ -83,4 +99,4 @@ namespace corsika::epos { } // namespace corsika::epos -#include <corsika/detail/modules/epos/Interaction.inl> +#include <corsika/detail/modules/epos/InteractionModel.inl> diff --git a/corsika/modules/proposal/Interaction.hpp b/corsika/modules/proposal/Interaction.hpp index b6f2fabbab60f772f69eb83bd9f78697a08fb8f8..3b769bc34281487885aab4f3e022e21a54fe564b 100644 --- a/corsika/modules/proposal/Interaction.hpp +++ b/corsika/modules/proposal/Interaction.hpp @@ -13,6 +13,7 @@ #include <corsika/framework/core/ParticleProperties.hpp> #include <corsika/framework/process/InteractionProcess.hpp> #include <corsika/framework/process/ProcessReturn.hpp> +#include <corsika/framework/geometry/FourVector.hpp> #include <corsika/framework/random/RNGManager.hpp> #include <corsika/framework/random/UniformRealDistribution.hpp> @@ -32,7 +33,7 @@ namespace corsika::proposal { std::unique_ptr<PROPOSAL::Interaction>>; std::unordered_map<calc_key_t, calculator_t, hash> - calc; //!< Stores the secondaries and interaction calculators. + calc_; //!< Stores the secondaries and interaction calculators. //! //! Build the secondaries and interaction calculators and add it to calc. @@ -53,13 +54,15 @@ namespace corsika::proposal { //! produce the corresponding secondaries and store them on the particle stack. //! template <typename TSecondaryView> - ProcessReturn doInteraction(TSecondaryView&); + ProcessReturn doInteraction(TSecondaryView&, Code const projectileId, + FourMomentum const& projectileP4); //! - //! Calculates the mean free path length + //! Calculates and returns the cross section. //! template <typename TParticle> - GrammageType getInteractionLength(TParticle const& p); + CrossSectionType getCrossSection(TParticle const& p, Code const projectileId, + FourMomentum const& projectileP4); }; } // namespace corsika::proposal diff --git a/corsika/modules/pythia8/Interaction.hpp b/corsika/modules/pythia8/Interaction.hpp index c099997a4019de54deb00980ef7349669b744e08..25ebf826b3f886065d5759fffa4eaa94f3e659ac 100644 --- a/corsika/modules/pythia8/Interaction.hpp +++ b/corsika/modules/pythia8/Interaction.hpp @@ -21,35 +21,76 @@ namespace corsika::pythia8 { class Interaction : public InteractionProcess<Interaction>, public Pythia8::Pythia { public: - Interaction(const bool print_listing = false); + Interaction(bool const print_listing = false); ~Interaction(); void setStable(std::vector<Code> const&); - void setUnstable(const Code); - void setStable(const Code); + void setUnstable(Code const); + void setStable(Code const); - bool isValidCoMEnergy(HEPEnergyType ecm) { return (10_GeV < ecm) && (ecm < 1_PeV); } + bool isValidCoMEnergy(HEPEnergyType const ecm) const { + return (10_GeV < ecm) && (ecm < 1_PeV); + } - bool canInteract(const Code); - void configureLabFrameCollision(const Code, const Code, const HEPEnergyType); + bool canInteract(Code const) const; + void configureLabFrameCollision(Code const, Code const, HEPEnergyType const); - std::tuple<CrossSectionType, CrossSectionType> getCrossSection( - const Code BeamId, const Code TargetId, const HEPEnergyType CoMenergy); + 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; - template <typename TParticle> - GrammageType getInteractionLength(TParticle 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 { + return std::get<0>( + getCrossSectionInelEla(projectile, target, projectileP4, targetP4)); + } /** - In this function PYTHIA is called to produce one event. The - event is copied (and boosted) into the shower lab frame. + * 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&); + void doInteraction(TView& output, Code const projectileId, Code const targetId, + FourMomentum const& projectileP4, FourMomentum const& targetP4); private: default_prng_type& RNG_ = RNGManager<>::getInstance().getRandomStream("pythia"); Pythia8::SigmaTotal sigma_; - const bool internalDecays_ = true; + bool const internalDecays_ = true; int count_ = 0; bool print_listing_ = false; }; diff --git a/corsika/modules/qgsjetII/Interaction.hpp b/corsika/modules/qgsjetII/Interaction.hpp deleted file mode 100644 index 0030de2fccc3438377c11a801e0206e62d55a0c0..0000000000000000000000000000000000000000 --- a/corsika/modules/qgsjetII/Interaction.hpp +++ /dev/null @@ -1,65 +0,0 @@ -/* - * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu - * - * This software is distributed under the terms of the GNU General Public - * Licence version 3 (GPL Version 3). See file LICENSE for a full version of - * the license. - */ - -#pragma once - -#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/qgsjetII/ParticleConversion.hpp> -#include <corsika/framework/utility/CorsikaData.hpp> - -#include <boost/filesystem/path.hpp> - -#include <qgsjet-II-04.hpp> -#include <string> - -namespace corsika::qgsjetII { - - class Interaction : public corsika::InteractionProcess<Interaction> { - - public: - Interaction(boost::filesystem::path dataPath = corsika_data("QGSJetII")); - ~Interaction(); - - bool wasInitialized() { return initialized_; } - unsigned int getMaxTargetMassNumber() const { return maxMassNumber_; } - bool isValidTarget(corsika::Code TargetId) const { - return is_nucleus(TargetId) && (get_nucleus_A(TargetId) < maxMassNumber_); - } - - CrossSectionType getCrossSection(const Code, const Code, const HEPEnergyType, - const unsigned int Abeam = 0, - const unsigned int Atarget = 0) const; - - template <typename TParticle> - GrammageType getInteractionLength(TParticle const&) const; - - /** - In this function QGSJETII is called to produce one event. The - event is copied (and boosted) into the shower lab frame. - */ - - template <typename TSecondaryView> - void doInteraction(TSecondaryView&); - - private: - int count_ = 0; - bool initialized_ = false; - QgsjetIIHadronType alternate_ = - QgsjetIIHadronType::PiPlusType; // for pi0, rho0 projectiles - - corsika::default_prng_type& rng_ = - corsika::RNGManager<>::getInstance().getRandomStream("qgsjet"); - static unsigned int constexpr maxMassNumber_ = 208; - }; - -} // namespace corsika::qgsjetII - -#include <corsika/detail/modules/qgsjetII/Interaction.inl> diff --git a/corsika/modules/qgsjetII/InteractionModel.hpp b/corsika/modules/qgsjetII/InteractionModel.hpp new file mode 100644 index 0000000000000000000000000000000000000000..0e3896df5f594874f15c3d4c132d8d41cb8e3192 --- /dev/null +++ b/corsika/modules/qgsjetII/InteractionModel.hpp @@ -0,0 +1,78 @@ +/* + * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu + * + * This software is distributed under the terms of the GNU General Public + * Licence version 3 (GPL Version 3). See file LICENSE for a full version ofp + * the license. + */ + +#pragma once + +#include <corsika/modules/qgsjetII/ParticleConversion.hpp> +#include <qgsjet-II-04.hpp> + +#include <corsika/framework/core/ParticleProperties.hpp> +#include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/framework/random/RNGManager.hpp> +#include <corsika/framework/utility/COMBoost.hpp> +#include <corsika/framework/utility/CorsikaData.hpp> + +#include <boost/filesystem/path.hpp> + +namespace corsika::qgsjetII { + + class InteractionModel { + + public: + InteractionModel(boost::filesystem::path dataPath = corsika_data("QGSJetII")); + ~InteractionModel(); + + /** + * Throws exception if invalid system is passed. + * + * @param beamId + * @param targetId + */ + bool isValid(Code const beamId, Code const targetId, HEPEnergyType const sqrtS) const; + + /** + * Return the QGSJETII 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 Aprojectile 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. + */ + CrossSectionType getCrossSection(Code const projectile, Code const target, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) const; + + /** + * In this function QGSJETII is called to produce one event. + * + * The event is copied (and boosted) into the shower lab frame. + */ + template <typename TSecondaries> + void doInteraction(TSecondaries&, Code const projectile, Code const target, + FourMomentum const& projectileP4, FourMomentum const& targetP4); + + private: + int count_ = 0; + QgsjetIIHadronType alternate_ = + QgsjetIIHadronType::PiPlusType; // for pi0, rho0 projectiles + + corsika::default_prng_type& rng_ = + corsika::RNGManager<>::getInstance().getRandomStream("qgsjet"); + static size_t constexpr maxMassNumber_ = 208; + static HEPEnergyType constexpr sqrtSmin_ = 10_GeV; + }; + +} // namespace corsika::qgsjetII + +#include <corsika/detail/modules/qgsjetII/InteractionModel.inl> diff --git a/corsika/modules/qgsjetII/ParticleConversion.hpp b/corsika/modules/qgsjetII/ParticleConversion.hpp index 3b4f689b8b7b1af73367f5f6a9355c273206d657..0a3ff8c08c74d2e3571bf49c562a1aef45bbc01e 100644 --- a/corsika/modules/qgsjetII/ParticleConversion.hpp +++ b/corsika/modules/qgsjetII/ParticleConversion.hpp @@ -15,13 +15,13 @@ namespace corsika::qgsjetII { /** - These are the possible secondaries produced by QGSJetII + * These are the possible secondaries produced by QGSJetII. */ enum class QgsjetIICode : int8_t; using QgsjetIICodeIntType = std::underlying_type<QgsjetIICode>::type; /** - These are the possible projectile for which QGSJetII knwos cross section + * These are the possible projectile for which QGSJetII knwos cross section. */ enum class QgsjetIIXSClass : int8_t { CannotInteract = 0, @@ -32,7 +32,7 @@ namespace corsika::qgsjetII { using QgsjetIIXSClassIntType = std::underlying_type<QgsjetIIXSClass>::type; /** - These are the only possible projectile types in QGSJetII + * These are the only possible projectile types in QGSJetII. */ enum class QgsjetIIHadronType : int8_t { UndefinedType = 0, @@ -58,7 +58,7 @@ namespace corsika::qgsjetII { namespace corsika::qgsjetII { - QgsjetIICode constexpr convertToQgsjetII(Code pCode) { + QgsjetIICode constexpr convertToQgsjetII(Code const pCode) { return corsika2qgsjetII[static_cast<CodeIntType>(pCode)]; } diff --git a/corsika/modules/sibyll/Interaction.hpp b/corsika/modules/sibyll/Interaction.hpp deleted file mode 100644 index b53fb63080b4a0a44b751a9abf9e73f60b940401..0000000000000000000000000000000000000000 --- a/corsika/modules/sibyll/Interaction.hpp +++ /dev/null @@ -1,73 +0,0 @@ -/* - * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu - * - * This software is distributed under the terms of the GNU General Public - * Licence version 3 (GPL Version 3). See file LICENSE for a full version of - * the license. - */ - -#pragma once - -#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 <tuple> - -namespace corsika::sibyll { - - class Interaction : public InteractionProcess<Interaction> { - - public: - Interaction(bool const sibyll_printout_on = false); - ~Interaction(); - - bool isValidCoMEnergy(HEPEnergyType const ecm) const { - return (minEnergyCoM_ <= ecm) && (ecm <= maxEnergyCoM_); - } - //! sibyll only accepts nuclei with 4<=A<=18 as targets, or protons aka Hydrogen or - //! neutrons (p,n == nucleon) - bool isValidTarget(Code const TargetId) const { - return (is_nucleus(TargetId) && (get_nucleus_A(TargetId) >= minNuclearTargetA_) && - (get_nucleus_A(TargetId) < maxTargetMassNumber_)) || - (TargetId == Code::Proton || TargetId == Code::Hydrogen || - TargetId == Code::Neutron); - } - - //! returns production and elastic cross section for hadrons in sibyll. Inputs are: - //! CorsikaId of beam particle, CorsikaId of target particle and center-of-mass - //! energy. Allowed targets are: nuclei or single nucleons (p,n,hydrogen). - std::tuple<CrossSectionType, CrossSectionType> getCrossSection( - Code const, Code const, HEPEnergyType const) const; - - template <typename TParticle> - GrammageType getInteractionLength(TParticle const&) const; - - /** - In this function SIBYLL is called to produce one event. The - event is copied (and boosted) into the shower lab frame. - */ - - template <typename TSecondaries> - void doInteraction(TSecondaries&); - - private: - unsigned int constexpr getMaxTargetMassNumber() const { return maxTargetMassNumber_; } - HEPEnergyType getMinEnergyCoM() const { return minEnergyCoM_; } - HEPEnergyType getMaxEnergyCoM() const { return maxEnergyCoM_; } - - default_prng_type& RNG_ = RNGManager<>::getInstance().getRandomStream("sibyll"); - const HEPEnergyType minEnergyCoM_ = 10. * 1e9 * electronvolt; - const HEPEnergyType maxEnergyCoM_ = 1.e6 * 1e9 * electronvolt; - static unsigned int constexpr maxTargetMassNumber_ = 18; - static unsigned int constexpr minNuclearTargetA_ = 4; - - // data members - int count_ = 0; - int nucCount_ = 0; - bool sibyll_listing_; - }; - -} // namespace corsika::sibyll - -#include <corsika/detail/modules/sibyll/Interaction.inl> diff --git a/corsika/modules/sibyll/InteractionModel.hpp b/corsika/modules/sibyll/InteractionModel.hpp new file mode 100644 index 0000000000000000000000000000000000000000..177e77857b305d385da162a3c0b705a8c5c276f9 --- /dev/null +++ b/corsika/modules/sibyll/InteractionModel.hpp @@ -0,0 +1,122 @@ +/* + * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu + * + * This software is distributed under the terms of the GNU General Public + * Licence version 3 (GPL Version 3). See file LICENSE for a full version of + * the license. + */ + +#pragma once + +#include <corsika/framework/core/ParticleProperties.hpp> +#include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/framework/random/RNGManager.hpp> +#include <corsika/framework/geometry/FourVector.hpp> + +#include <tuple> + +namespace corsika::sibyll { + + /** + * @brief sibyll::InteractionModel provides the SIBYLL proton-nucleus interaction model. + * + * This is a TModel argument for InteractionProcess<TModel>. + */ + + class InteractionModel { + + public: + InteractionModel(); + ~InteractionModel(); + + /** + * @brief Set the Verbose flag. + * + * If flag is true, SIBYLL will printout additional secondary particle information + * lists, etc. + * + * @param flag to switch. + */ + void setVerbose(bool const flag); + + /** + * @brief evaluated validity of collision system. + * + * sibyll only accepts nuclei with 4<=A<=18 as targets, or protons aka Hydrogen or + * neutrons (p,n == nucleon). + */ + bool constexpr isValid(Code const projectileId, Code const targetId, + HEPEnergyType const sqrtSnn) 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 { + return std::get<0>( + getCrossSectionInelEla(projectile, target, projectileP4, targetP4)); + } + + /** + * In this function SIBYLL is called to produce one event. The + * event is copied (and boosted) into the shower lab frame. + */ + + template <typename TSecondaries> + void doInteraction(TSecondaries& view, Code const projectile, Code const target, + FourMomentum const& projectileP4, FourMomentum const& targetP4); + + private: + HEPEnergyType constexpr getMinEnergyCoM() const { return minEnergyCoM_; } + HEPEnergyType constexpr getMaxEnergyCoM() const { return maxEnergyCoM_; } + + // hard model limits + static HEPEnergyType constexpr minEnergyCoM_ = 10. * 1e9 * electronvolt; + static HEPEnergyType constexpr maxEnergyCoM_ = 1.e6 * 1e9 * electronvolt; + static unsigned int constexpr maxTargetMassNumber_ = 18; + static unsigned int constexpr minNuclearTargetA_ = 4; + + default_prng_type& RNG_ = RNGManager<>::getInstance().getRandomStream("sibyll"); + + // data members + int count_ = 0; + int nucCount_ = 0; + bool sibyll_listing_; + }; + +} // namespace corsika::sibyll + +#include <corsika/detail/modules/sibyll/InteractionModel.inl> diff --git a/corsika/modules/sibyll/NuclearInteraction.hpp b/corsika/modules/sibyll/NuclearInteraction.hpp deleted file mode 100644 index 7196363131bd7bca26961bbe427d995927e134d0..0000000000000000000000000000000000000000 --- a/corsika/modules/sibyll/NuclearInteraction.hpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu - * - * This software is distributed under the terms of the GNU General Public - * Licence version 3 (GPL Version 3). See file LICENSE for a full version of - * the license. - */ - -#pragma once - -#include <corsika/framework/core/ParticleProperties.hpp> -#include <corsika/framework/random/RNGManager.hpp> -#include <corsika/framework/process/InteractionProcess.hpp> - -namespace corsika::sibyll { - - class Interaction; // fwd-decl - - /** - * - * - **/ - template <class TEnvironment> - class NuclearInteraction : public InteractionProcess<NuclearInteraction<TEnvironment>> { - - public: - NuclearInteraction(sibyll::Interaction&, TEnvironment const&); - ~NuclearInteraction(); - - void initializeNuclearCrossSections(); - void printCrossSectionTable(Code); - CrossSectionType readCrossSectionTable(int const, Code const, HEPEnergyType const); - HEPEnergyType getMinEnergyPerNucleonCoM() { return gMinEnergyPerNucleonCoM_; } - HEPEnergyType getMaxEnergyPerNucleonCoM() { return gMaxEnergyPerNucleonCoM_; } - unsigned int constexpr getMaxNucleusAProjectile() { return gMaxNucleusAProjectile_; } - unsigned int constexpr getMaxNFragments() { return gMaxNFragments_; } - unsigned int constexpr getNEnergyBins() { return gNEnBins_; } - - template <typename Particle> - std::tuple<CrossSectionType, CrossSectionType> getCrossSection(Particle const& p, - const Code TargetId); - - template <typename Particle> - GrammageType getInteractionLength(Particle const&); - - template <typename TSecondaryView> - void doInteraction(TSecondaryView&); - - private: - int count_ = 0; - int nucCount_ = 0; - - TEnvironment const& environment_; - sibyll::Interaction& hadronicInteraction_; - std::map<Code, int> targetComponentsIndex_; - default_prng_type& RNG_ = RNGManager<>::getInstance().getRandomStream("sibyll"); - static unsigned int constexpr gNSample_ = - 500; // number of samples in MC estimation of cross section - static unsigned int constexpr gMaxNucleusAProjectile_ = 56; - static unsigned int constexpr gNEnBins_ = 6; - static unsigned int constexpr gMaxNFragments_ = 60; - // energy limits defined by table used for cross section in signuc.f - // 10**1 GeV to 10**6 GeV - static HEPEnergyType constexpr gMinEnergyPerNucleonCoM_ = 10. * 1e9 * electronvolt; - static HEPEnergyType constexpr gMaxEnergyPerNucleonCoM_ = 1.e6 * 1e9 * electronvolt; - }; - -} // namespace corsika::sibyll - -#include <corsika/detail/modules/sibyll/NuclearInteraction.inl> diff --git a/corsika/modules/sibyll/NuclearInteractionModel.hpp b/corsika/modules/sibyll/NuclearInteractionModel.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2d7d81c2ce0717e33567715b554b93fb93ce15c9 --- /dev/null +++ b/corsika/modules/sibyll/NuclearInteractionModel.hpp @@ -0,0 +1,76 @@ +/* + * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu + * + * This software is distributed under the terms of the GNU General Public + * Licence version 3 (GPL Version 3). See file LICENSE for a full version of + * the license. + */ + +#pragma once + +#include <corsika/framework/core/ParticleProperties.hpp> +#include <corsika/framework/random/RNGManager.hpp> +#include <corsika/framework/geometry/FourVector.hpp> + +namespace corsika::sibyll { + + /** + * The sibyll::NuclearInteractionModel provides the SIBYLL semi superposition model. + * + * can transform a proton-nucleus interaction model into a nucleus-nucleus interaction + * model. + * + * @tparam TNucleonModel + */ + template <class TEnvironment, class TNucleonModel> + class NuclearInteractionModel { + + public: + NuclearInteractionModel(TNucleonModel&, TEnvironment const&); + ~NuclearInteractionModel(); + + bool constexpr isValid(Code const projectileId, Code const targetId, + HEPEnergyType const sqrtSnn) const; + + void initializeNuclearCrossSections(); + void printCrossSectionTable(Code) const; + CrossSectionType readCrossSectionTable(int const, Code const, + HEPEnergyType const) const; + HEPEnergyType getMinEnergyPerNucleonCoM() const { return gMinEnergyPerNucleonCoM_; } + HEPEnergyType getMaxEnergyPerNucleonCoM() const { return gMaxEnergyPerNucleonCoM_; } + unsigned int constexpr getMaxNucleusAProjectile() const { + return gMaxNucleusAProjectile_; + } + unsigned int constexpr getMaxNFragments() const { return gMaxNFragments_; } + unsigned int constexpr getNEnergyBins() const { return gNEnBins_; } + + CrossSectionType getCrossSection(Code const, Code const, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) const; + + template <typename TSecondaryView> + void doInteraction(TSecondaryView&, Code const, Code const, + FourMomentum const& projectileP4, FourMomentum const& targetP4); + + private: + int count_ = 0; + int nucCount_ = 0; + + TEnvironment const& environment_; + TNucleonModel& hadronicInteraction_; + std::map<Code, int> targetComponentsIndex_; + default_prng_type& RNG_ = RNGManager<>::getInstance().getRandomStream("sibyll"); + static unsigned int constexpr gNSample_ = + 500; // number of samples in MC estimation of cross section + static unsigned int constexpr gMaxNucleusAProjectile_ = 56; + static unsigned int constexpr gNEnBins_ = 6; + static unsigned int constexpr gMaxNFragments_ = 60; + // energy limits defined by table used for cross section in signuc.f + // 10**1 GeV to 10**6 GeV + static HEPEnergyType constexpr gMinEnergyPerNucleonCoM_ = 10. * 1e9 * electronvolt; + static HEPEnergyType constexpr gMaxEnergyPerNucleonCoM_ = 1.e6 * 1e9 * electronvolt; + }; + +} // namespace corsika::sibyll + +#include <corsika/detail/modules/sibyll/NuclearInteractionModel.inl> diff --git a/corsika/modules/sibyll/ParticleConversion.hpp b/corsika/modules/sibyll/ParticleConversion.hpp index 653b83fc3c05304c509ee394f592b6783f0b058e..86b0a83d5eba4fabe6fe18d1d1c720abde49eb05 100644 --- a/corsika/modules/sibyll/ParticleConversion.hpp +++ b/corsika/modules/sibyll/ParticleConversion.hpp @@ -33,14 +33,14 @@ namespace corsika::sibyll { #include <corsika/modules/sibyll/Generated.inc> - SibyllCode constexpr convertToSibyll(corsika::Code pCode) { - return corsika2sibyll[static_cast<corsika::CodeIntType>(pCode)]; + SibyllCode constexpr convertToSibyll(Code const pCode) { + return corsika2sibyll[static_cast<CodeIntType>(pCode)]; } - corsika::Code constexpr convertFromSibyll(SibyllCode pCode) { + Code constexpr convertFromSibyll(SibyllCode const pCode) { auto const s = static_cast<SibyllCodeIntType>(pCode); auto const corsikaCode = sibyll2corsika[s - minSibyll]; - if (corsikaCode == corsika::Code::Unknown) { + if (corsikaCode == Code::Unknown) { throw std::runtime_error(std::string("SIBYLL/CORSIKA conversion of ") .append(std::to_string(s)) .append(" impossible")); @@ -53,13 +53,15 @@ namespace corsika::sibyll { } int constexpr getSibyllXSCode(Code const code) { + if (is_nucleus(code)) + return static_cast<SibyllXSClassIntType>(SibyllXSClass::CannotInteract); return static_cast<SibyllXSClassIntType>( - corsika2sibyllXStype[static_cast<corsika::CodeIntType>(code)]); + corsika2sibyllXStype[static_cast<CodeIntType>(code)]); } - bool constexpr canInteract(corsika::Code pCode) { return getSibyllXSCode(pCode) > 0; } + bool constexpr canInteract(Code const pCode) { return getSibyllXSCode(pCode) > 0; } - HEPMassType getSibyllMass(corsika::Code const); + HEPMassType getSibyllMass(Code const); } // namespace corsika::sibyll diff --git a/corsika/modules/urqmd/ParticleConversion.hpp b/corsika/modules/urqmd/ParticleConversion.hpp new file mode 100644 index 0000000000000000000000000000000000000000..85ae95d0d47bb85cd49ce369983ee42156fda815 --- /dev/null +++ b/corsika/modules/urqmd/ParticleConversion.hpp @@ -0,0 +1,38 @@ +/* + * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu + * + * This software is distributed under the terms of the GNU General Public + * Licence version 3 (GPL Version 3). See file LICENSE for a full version of + * the license. + */ + +#pragma once + +#include <corsika/framework/core/ParticleProperties.hpp> + +#include <array> +#include <utility> +#include <string> + +namespace corsika::urqmd { + + /** + * Checks if particle with Code can interaction in UrQMD. + */ + bool canInteract(Code const); + + /** + * convert CORSIKA code to UrQMD code tuple. + * + * In the current implementation a detour via the PDG code is made. + */ + std::pair<int, int> convertToUrQMD(Code const); + + /** + * convert UrQMD code to CORSIKA code. + */ + Code convertFromUrQMD(int vItyp, int vIso3); + +} // namespace corsika::urqmd + +#include <corsika/detail/modules/urqmd/ParticleConversion.inl> diff --git a/corsika/modules/urqmd/UrQMD.hpp b/corsika/modules/urqmd/UrQMD.hpp index 2f3c278043fa83728d36fdc52b8ca181053031ad..1ae0ff80af53f99ce31bbb383a459510e362a3e1 100644 --- a/corsika/modules/urqmd/UrQMD.hpp +++ b/corsika/modules/urqmd/UrQMD.hpp @@ -10,9 +10,9 @@ #include <corsika/framework/core/ParticleProperties.hpp> #include <corsika/framework/core/PhysicalUnits.hpp> -#include <corsika/framework/process/InteractionProcess.hpp> #include <corsika/framework/random/RNGManager.hpp> #include <corsika/framework/utility/CorsikaData.hpp> +#include <corsika/framework/geometry/FourVector.hpp> #include <boost/filesystem/path.hpp> #include <boost/multi_array.hpp> @@ -23,32 +23,30 @@ namespace corsika::urqmd { - class UrQMD : public InteractionProcess<UrQMD> { + class UrQMD { public: /** - * @param path Location of UrQMD XS data file + * The UrQMD interaction model. + * + * @param path Location of UrQMD XS data file. * @param retryFlag Internal UrQMD flag for retrying interaction in case of empty - * event, 0 means retry + * event, 0 means retry. */ UrQMD(boost::filesystem::path const path = corsika_data("UrQMD/UrQMD-1.3.1-xs.dat"), int const retryFlag = 0); - template <typename TParticle> - GrammageType getInteractionLength(TParticle const&) const; + bool isValid(Code const projectileId, Code const targetId) const; - CrossSectionType getTabulatedCrossSection(Code, Code, HEPEnergyType) const; + CrossSectionType getTabulatedCrossSection(Code const, Code const, + HEPEnergyType const) const; - template <typename TParticle> - CrossSectionType getCrossSection(TParticle const&, Code) const; + CrossSectionType getCrossSection(Code const projectileId, Code const targetId, + FourMomentum const& projP4, + FourMomentum const& targP4) const; template <typename TView> - void doInteraction(TView&); - - bool canInteract(Code) const; - - void blob(int) {} - - static CrossSectionType getCrossSection(Code, Code, HEPEnergyType, int); + void doInteraction(TView&, Code const projectile, Code const targetId, + FourMomentum const& projP4, FourMomentum const& targP4); private: void readXSFile(boost::filesystem::path); @@ -60,14 +58,6 @@ namespace corsika::urqmd { boost::multi_array<CrossSectionType, 3> xs_interp_support_table_; }; - /** - * convert CORSIKA code to UrQMD code tuple - * - * In the current implementation a detour via the PDG code is made. - */ - std::pair<int, int> convertToUrQMD(Code); - Code convertFromUrQMD(int vItyp, int vIso3); - } // namespace corsika::urqmd #include <corsika/detail/modules/urqmd/UrQMD.inl> diff --git a/corsika/stack/VectorStack.hpp b/corsika/stack/VectorStack.hpp index 2a96ef3df912765c7fc81f67aceceb24cb7c6f07..22873790c22c1e478a0c6883008ed95e54a0562b 100644 --- a/corsika/stack/VectorStack.hpp +++ b/corsika/stack/VectorStack.hpp @@ -53,31 +53,29 @@ namespace corsika { /** * Set data of new particle. * - * @param p parent particle + * @param parent parent particle * @param v tuple containing: PID, Momentum Vector, Position, Time * * MomentumVector is only used to determine the DirectionVector, the normalization * is lost. */ - void setParticleData(ParticleInterface<TStackIterator> const& p, + void setParticleData(ParticleInterface<TStackIterator> const& parent, particle_data_type const& v); /** * Set data of new particle. * * @param v tuple containing: PID, kinetic Energy, Direction Vector, Position, Time - * */ void setParticleData(particle_data_momentum_type const& v); /** * Set data of new particle. * - * @param p parent particle + * @param parent parent particle * @param v tuple containing: PID, kinetic Energy, Direction Vector, Position, Time - * */ - void setParticleData(ParticleInterface<TStackIterator> const& p, + void setParticleData(ParticleInterface<TStackIterator> const& parent, particle_data_momentum_type const& v); ///! Set particle corsika::Code @@ -97,9 +95,9 @@ namespace corsika { } /** - The MomentumVector v is used to determine the DirectionVector, and to update the - particle energy. - */ + * The MomentumVector v is used to determine the DirectionVector, and to update the + * particle energy. + */ void setMomentum(MomentumVector const& v) { HEPMomentumType const P = v.getNorm(); if (P == 0_eV) { diff --git a/examples/boundary_example.cpp b/examples/boundary_example.cpp index 6e369e8a2085c66a9e9f3e31da4db3d8dc3eb9ed..8b28769038504483db2cc3b516d62f162d2dbd10 100644 --- a/examples/boundary_example.cpp +++ b/examples/boundary_example.cpp @@ -103,7 +103,7 @@ int main() { auto const props = world->setModelProperties<MyHomogeneousModel>( Medium::AirDry1Atm, Vector(rootCS, 0_T, 0_T, 0_T), 1_kg / (1_m * 1_m * 1_m), - NuclearComposition(std::vector<Code>{Code::Proton}, std::vector<float>{1.f})); + NuclearComposition({Code::Proton}, {1.})); // add a "target" sphere with 5km readius at 0,0,0 auto target = EnvType::createNode<Sphere>(Point{rootCS, 0_m, 0_m, 0_m}, 5_km); diff --git a/examples/cascade_example.cpp b/examples/cascade_example.cpp index dde77011d5888a9266b5d9a981f44a1d5d8cfd91..cf01b33ca499cf8d1df3d61bcc142f0aee921cdf 100644 --- a/examples/cascade_example.cpp +++ b/examples/cascade_example.cpp @@ -78,12 +78,11 @@ int main() { UniformMagneticField<HomogeneousMedium<setup::EnvironmentInterface>>>; // fraction of oxygen - float const fox = 0.20946; + double const fox = 0.20946; 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(std::vector<Code>{Code::Nitrogen, Code::Oxygen}, - std::vector<float>{1.f - fox, fox})); + NuclearComposition({Code::Nitrogen, Code::Oxygen}, {1. - fox, fox})); auto innerMedium = setup::Environment::createNode<Sphere>(Point{rootCS, 0_m, 0_m, 0_m}, 5000_m); @@ -147,11 +146,8 @@ int main() { BetheBlochPDG eLoss{showerAxis}; // assemble all processes into an ordered process list - auto sequence = make_sequence( - stackInspect, - make_select([](auto const& particle) { return is_nucleus(particle.getPID()); }, - sibyllNuc, sibyll), - decay, eLoss, cut, trackWriter); + auto sequence = make_sequence(stackInspect, make_sequence(sibyllNuc, sibyll), decay, + eLoss, cut, trackWriter); // define air shower object, run simulation Cascade EAS(env, tracking, sequence, output, stack); diff --git a/examples/cascade_proton_example.cpp b/examples/cascade_proton_example.cpp index 053a0c13e2529bd1ae1883fa0b52b661413b2c3c..fda0a3af6062b3011ee073c30e7fd2438349ae91 100644 --- a/examples/cascade_proton_example.cpp +++ b/examples/cascade_proton_example.cpp @@ -80,9 +80,7 @@ int main() { world->setModelProperties<MyHomogeneousModel>( Medium::AirDry1Atm, MagneticFieldVector(rootCS, 0_T, 0_T, 1_mT), - 1_kg / (1_m * 1_m * 1_m), - NuclearComposition(std::vector<Code>{Code::Hydrogen}, - std::vector<float>{(float)1.})); + 1_kg / (1_m * 1_m * 1_m), NuclearComposition({Code::Hydrogen}, {1.})); universe.addChild(std::move(world)); diff --git a/examples/corsika.cpp b/examples/corsika.cpp index a3db1ccfa0c4d35899c25d9603e9655c69a7443d..7df6dcbde54c0d40190da9a377c166d9472c588b 100644 --- a/examples/corsika.cpp +++ b/examples/corsika.cpp @@ -240,11 +240,11 @@ int main(int argc, char** argv) { HEPEnergyType mass = get_mass(beamCode); // particle energy - HEPEnergyType const E0 = 1_GeV * app["--energy"]->as<float>(); + HEPEnergyType const E0 = 1_GeV * app["--energy"]->as<double>(); // direction of the shower in (theta, phi) space - auto const thetaRad = app["--zenith"]->as<float>() / 180. * M_PI; - auto const phiRad = app["--azimuth"]->as<float>() / 180. * M_PI; + auto const thetaRad = app["--zenith"]->as<double>() / 180. * M_PI; + auto const phiRad = app["--azimuth"]->as<double>() / 180. * M_PI; // convert Elab to Plab HEPMomentumType P0 = sqrt((E0 - mass) * (E0 + mass)); @@ -286,8 +286,7 @@ int main(int argc, char** argv) { InteractionCounter sibyllCounted(sibyll); corsika::sibyll::NuclearInteraction sibyllNuc(sibyll, env); InteractionCounter sibyllNucCounted(sibyllNuc); - auto heModelCounted = make_select([](auto const& p) { return is_nucleus(p.getPID()); }, - sibyllNucCounted, sibyllCounted); + auto heModelCounted = make_sequence(sibyllNucCounted, sibyllCounted); corsika::pythia8::Decay decayPythia; @@ -317,8 +316,10 @@ int main(int argc, char** argv) { HEPEnergyType const emcut = 1_GeV; HEPEnergyType const hadcut = 1_GeV; ParticleCut cut(emcut, emcut, hadcut, hadcut, true); + corsika::proposal::Interaction emCascade(env); - InteractionCounter emCascadeCounted(emCascade); + // NOT available for PROPOSAL due to interface trouble: + // InteractionCounter emCascadeCounted(emCascade); // corsika::proposal::ContinuousProcess emContinuous(env); BetheBlochPDG emContinuous(showerAxis); @@ -335,7 +336,7 @@ int main(int argc, char** argv) { HEPEnergyType cutE_; EnergySwitch(HEPEnergyType cutE) : cutE_(cutE) {} - bool operator()(const Particle& p) { return (p.getKineticEnergy() < cutE_); } + bool operator()(const Particle& p) const { return (p.getKineticEnergy() < cutE_); } }; auto hadronSequence = make_select(EnergySwitch(63.1_GeV), urqmdCounted, heModelCounted); auto decaySequence = make_sequence(decayPythia, decaySibyll); @@ -352,8 +353,8 @@ int main(int argc, char** argv) { output.add("particles", observationLevel); // assemble the final process sequence - auto sequence = make_sequence(stackInspect, hadronSequence, decaySequence, - emCascadeCounted, cut, emContinuous, // trackWriter, + auto sequence = make_sequence(stackInspect, hadronSequence, decaySequence, cut, + emCascade, emContinuous, // trackWriter, observationLevel, longprof); /* === END: SETUP PROCESS LIST === */ diff --git a/examples/em_shower.cpp b/examples/em_shower.cpp index 62aa91e564010d12a0fbcb3ab48062917ca42da4..b01d7b608b85d661d7fdce6004a1ef9efcd3cd4b 100644 --- a/examples/em_shower.cpp +++ b/examples/em_shower.cpp @@ -144,7 +144,9 @@ int main(int argc, char** argv) { ParticleCut cut(60_GeV, 60_GeV, 100_PeV, 100_PeV, true); corsika::proposal::Interaction emCascade(env); corsika::proposal::ContinuousProcess emContinuous(env); - InteractionCounter emCascadeCounted(emCascade); + + // NOT possible right now, due to interface differenc in PROPOSAL + // InteractionCounter emCascadeCounted(emCascade); TrackWriter trackWriter; output.add("tracks", trackWriter); // register TrackWriter @@ -157,8 +159,8 @@ int main(int argc, char** argv) { obsPlane, DirectionVector(rootCS, {1., 0., 0.}), "particles.dat"); output.add("obsplane", observationLevel); - auto sequence = make_sequence(emCascadeCounted, emContinuous, longprof, cut, - observationLevel, trackWriter); + auto sequence = make_sequence(emCascade, emContinuous, longprof, cut, observationLevel, + trackWriter); // define air shower object, run simulation setup::Tracking tracking; Cascade EAS(env, tracking, sequence, output, stack); @@ -183,9 +185,6 @@ int main(int argc, char** argv) { cut.reset(); emContinuous.reset(); - auto const hists = emCascadeCounted.getHistogram(); - save_hist(hists.labHist(), "inthist_lab_emShower.npz", true); - save_hist(hists.CMSHist(), "inthist_cms_emShower.npz", true); longprof.save("longprof_emShower.txt"); output.endOfLibrary(); diff --git a/examples/hybrid_MC.cpp b/examples/hybrid_MC.cpp index 4a85351b0e9e5ebdb4c24910a9fa77655c14215a..0ff10af164ff51fc3db6cde5bbb7324dea410d24 100644 --- a/examples/hybrid_MC.cpp +++ b/examples/hybrid_MC.cpp @@ -248,7 +248,7 @@ int main(int argc, char** argv) { HEPEnergyType cutE_; EnergySwitch(HEPEnergyType cutE) : cutE_(cutE) {} - bool operator()(const setup::Stack::particle_type& p) { + bool operator()(const setup::Stack::particle_type& p) const { return (p.getEnergy() < cutE_); } }; diff --git a/examples/mars.cpp b/examples/mars.cpp index 14eab4096b2c07aa34d65ffa7fafdc3e12d3f1e7..afa8c71b70b8e2526785312acc38e1c7b684484c 100644 --- a/examples/mars.cpp +++ b/examples/mars.cpp @@ -271,11 +271,11 @@ int main(int argc, char** argv) { HEPEnergyType const mass = get_mass(beamCode); // particle energy - HEPEnergyType const E0 = 1_GeV * app["--energy"]->as<float>(); + HEPEnergyType const E0 = 1_GeV * app["--energy"]->as<double>(); // direction of the shower in (theta, phi) space - auto const thetaRad = app["--zenith"]->as<float>() / 180. * M_PI; - auto const phiRad = app["--azimuth"]->as<float>() / 180. * M_PI; + auto const thetaRad = app["--zenith"]->as<double>() / 180. * M_PI; + auto const phiRad = app["--azimuth"]->as<double>() / 180. * M_PI; // convert Elab to Plab HEPMomentumType P0 = sqrt((E0 - mass) * (E0 + mass)); @@ -313,8 +313,7 @@ int main(int argc, char** argv) { InteractionCounter sibyllCounted(sibyll); corsika::sibyll::NuclearInteraction sibyllNuc(sibyll, env); InteractionCounter sibyllNucCounted(sibyllNuc); - auto heModelCounted = make_select([](auto const& p) { return is_nucleus(p.getPID()); }, - sibyllNucCounted, sibyllCounted); + auto heModelCounted = make_sequence(sibyllNucCounted, sibyllCounted); corsika::pythia8::Decay decayPythia; @@ -345,7 +344,8 @@ int main(int argc, char** argv) { HEPEnergyType const hadcut = 1_GeV; ParticleCut cut(emcut, emcut, hadcut, hadcut, true); corsika::proposal::Interaction emCascade(env); - InteractionCounter emCascadeCounted(emCascade); + // NOT possible right now, due to interface difference for PROPOSAL: + // InteractionCounter emCascadeCounted(emCascade); // corsika::proposal::ContinuousProcess emContinuous(env); BetheBlochPDG emContinuous(showerAxis); @@ -360,7 +360,7 @@ int main(int argc, char** argv) { HEPEnergyType cutE_; EnergySwitch(HEPEnergyType cutE) : cutE_(cutE) {} - bool operator()(const Particle& p) { return (p.getKineticEnergy() < cutE_); } + bool operator()(Particle const& p) const { return (p.getKineticEnergy() < cutE_); } }; auto hadronSequence = make_select(EnergySwitch(63.1_GeV), urqmdCounted, heModelCounted); auto decaySequence = make_sequence(decayPythia, decaySibyll); @@ -378,8 +378,8 @@ int main(int argc, char** argv) { // assemble the final process sequence auto sequence = - make_sequence(stackInspect, hadronSequence, decaySequence, emCascadeCounted, - emContinuous, cut, trackWriter, observationLevel, longprof); + make_sequence(stackInspect, hadronSequence, decaySequence, emCascade, emContinuous, + cut, trackWriter, observationLevel, longprof); /* === END: SETUP PROCESS LIST === */ // create the cascade object using the default stack and tracking implementation diff --git a/examples/staticsequence_example.cpp b/examples/staticsequence_example.cpp index 3f6c5aff75c72ec9058773fabbb6347e0db15274..8f95e66c486eaed05a913176d7cac77e152388c3 100644 --- a/examples/staticsequence_example.cpp +++ b/examples/staticsequence_example.cpp @@ -105,7 +105,6 @@ void modular() { int main() { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); std::cout << "staticsequence_example" << std::endl; diff --git a/examples/vertical_EAS.cpp b/examples/vertical_EAS.cpp index 911f45c528f6f440c45b7a94048c2e3478b5a8a3..6bb97291aa2b2d7d3106fcf68a6b887aaf99c7d9 100644 --- a/examples/vertical_EAS.cpp +++ b/examples/vertical_EAS.cpp @@ -236,12 +236,10 @@ int main(int argc, char** argv) { HEPEnergyType cutE_; EnergySwitch(HEPEnergyType cutE) : cutE_(cutE) {} - bool operator()(const Particle& p) { return (p.getEnergy() < cutE_); } + bool operator()(const Particle& p) const { return (p.getEnergy() < cutE_); } }; - auto hadronSequence = - make_select(EnergySwitch(55_GeV), urqmdCounted, - make_select([](auto const& p) { return is_nucleus(p.getPID()); }, - sibyllNucCounted, sibyllCounted)); + auto hadronSequence = make_select(EnergySwitch(55_GeV), urqmdCounted, + make_sequence(sibyllNucCounted, sibyllCounted)); auto decaySequence = make_sequence(decayPythia, decaySibyll); // directory for outputs diff --git a/modules/sibyll/CMakeLists.txt b/modules/sibyll/CMakeLists.txt index 7f03881a4462e66016290e218fe52d46021a95e3..21e45060dbf71e4cb71a9a6c4fbdac21d1d34098 100644 --- a/modules/sibyll/CMakeLists.txt +++ b/modules/sibyll/CMakeLists.txt @@ -15,6 +15,7 @@ set ( enable_language (Fortran) add_library (Sibyll_static STATIC ${MODEL_SOURCES}) +add_library (Sibyll SHARED ${MODEL_SOURCES}) set_target_properties ( Sibyll_static @@ -28,13 +29,24 @@ target_include_directories ( $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> $<INSTALL_INTERFACE:include/corsika_modules/sibyll> ) + target_include_directories ( + Sibyll + PUBLIC + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> + $<INSTALL_INTERFACE:include/corsika_modules/sibyll> + ) target_link_libraries ( Sibyll_static PUBLIC gfortran ) - +target_link_libraries ( + Sibyll + PUBLIC + gfortran + ) + install ( FILES ${MODEL_HEADERS} @@ -42,7 +54,7 @@ install ( ) install ( - TARGETS Sibyll_static + TARGETS Sibyll_static Sibyll EXPORT CORSIKA8PublicTargets ARCHIVE DESTINATION lib/corsika ) diff --git a/tests/common/SetupTestEnvironment.hpp b/tests/common/SetupTestEnvironment.hpp index fa48ec023fc37358198582f7f15139604d4cfa36..358ed6df265851c96198e8f013b03e71a3cae963 100644 --- a/tests/common/SetupTestEnvironment.hpp +++ b/tests/common/SetupTestEnvironment.hpp @@ -49,7 +49,7 @@ namespace corsika::setup::testing { world->setModelProperties<MyHomogeneousModel>( Medium::AirDry1Atm, Vector(cs, 0_T, 0_T, BfieldZ), 1_kg / (1_m * 1_m * 1_m), - NuclearComposition(std::vector<Code>{vTargetCode}, std::vector<float>{1.})); + NuclearComposition(std::vector<Code>{vTargetCode}, std::vector<double>{1.})); setup::Environment::BaseNodeType* nodePtr = world.get(); universe.addChild(std::move(world)); diff --git a/tests/common/SetupTestStack.hpp b/tests/common/SetupTestStack.hpp index a5bfb3d23622a3da7f0a1ea4102f514735005906..a6141e582262b77e4062eb020bfdc7e3a19adece 100644 --- a/tests/common/SetupTestStack.hpp +++ b/tests/common/SetupTestStack.hpp @@ -18,7 +18,7 @@ * \file SetupTestStack * * standard stack setup for unit tests. - **/ + */ namespace corsika::setup::testing { @@ -32,10 +32,10 @@ namespace corsika::setup::testing { * * \return a tuple with element 0 being a Stack object filled with * one particle, and element 1 the StackView on it. - **/ + */ inline std::tuple<std::unique_ptr<setup::Stack>, std::unique_ptr<setup::StackView>> - setup_stack(Code vProjectileType, HEPEnergyType vMomentum, + setup_stack(Code const vProjectileType, HEPEnergyType const vMomentum, setup::Environment::BaseNodeType* const vNodePtr, CoordinateSystemPtr const& cs) { diff --git a/tests/framework/testCOMBoost.cpp b/tests/framework/testCOMBoost.cpp index d092a4b61ea46dfbcb734a4c8f31013550451204..f9542e3942c61d8430f567048e25d3f8bfd6b54a 100644 --- a/tests/framework/testCOMBoost.cpp +++ b/tests/framework/testCOMBoost.cpp @@ -23,17 +23,19 @@ CoordinateSystemPtr rootCS = get_root_CoordinateSystem(); /** * \todo such helper functions should be moved to the FourVector class: - **/ + */ // helper function for energy-momentum // relativistic energy -auto const energy = [](HEPMassType m, MomentumVector const& p) { +auto const energy = [](HEPMassType const m, MomentumVector const& p) { return sqrt(m * m + p.getSquaredNorm()); }; -auto const momentum = [](HEPEnergyType E, HEPMassType m) { return sqrt(E * E - m * m); }; +auto const momentum = [](HEPEnergyType const E, HEPMassType const m) { + return sqrt(E * E - m * m); +}; // helper function for mandelstam-s -auto const s = [](HEPEnergyType E, QuantityVector<hepmomentum_d> const& p) { +auto const s = [](HEPEnergyType const E, QuantityVector<hepmomentum_d> const& p) { return E * E - p.getSquaredNorm(); }; @@ -170,7 +172,7 @@ TEST_CASE("rotation") { TEST_CASE("boosts") { - logging::set_level(logging::level::info); + logging::set_level(logging::level::trace); // define target kinematics in lab frame HEPMassType const targetMass = 1_GeV; @@ -322,6 +324,49 @@ TEST_CASE("boosts") { PprojCoM.getSpaceLikeComponents() + PtargCoM.getSpaceLikeComponents(); CHECK(sumPCoM.getNorm() / P0 == Approx(0).margin(absMargin)); // MAKE RELATIVE CHECK } + + SECTION("CoM system") { + + MomentumVector pCM{rootCS, 0_GeV, 0_GeV, 5_GeV}; + + COMBoost boostCMS({energy(1_GeV, pCM), pCM}, {energy(1_GeV, pCM), -pCM}); + + auto test1 = boostCMS.fromCoM(FourMomentum{ + 0_GeV, MomentumVector(boostCMS.getOriginalCS(), {0_GeV, 0_GeV, 0_GeV})}); + CHECK(test1.getNorm() == 0_GeV); + auto test2 = boostCMS.fromCoM(FourMomentum{ + 0_GeV, MomentumVector(boostCMS.getRotatedCS(), {0_GeV, 0_GeV, 0_GeV})}); + CHECK(test2.getNorm() == 0_GeV); + + auto test3 = boostCMS.toCoM(FourMomentum{ + 0_GeV, MomentumVector(boostCMS.getOriginalCS(), {0_GeV, 0_GeV, 0_GeV})}); + CHECK(test3.getNorm() == 0_GeV); + auto test4 = boostCMS.toCoM(FourMomentum{ + 0_GeV, MomentumVector(boostCMS.getRotatedCS(), {0_GeV, 0_GeV, 0_GeV})}); + CHECK(test4.getNorm() == 0_GeV); + + HEPEnergyType const sqrtS = + (FourMomentum{energy(1_GeV, pCM), pCM} + FourMomentum{energy(1_GeV, pCM), -pCM}) + .getNorm(); + HEPEnergyType const eLab = + (static_pow<2>(sqrtS) - 2 * static_pow<2>(1_GeV)) / (2 * 1_GeV); + COMBoost boostLab({eLab, MomentumVector{rootCS, momentum(eLab, 1_GeV), 0_eV, 0_eV}}, + {1_GeV, MomentumVector{rootCS, 0_eV, 0_eV, 0_eV}}); + + FourMomentum p4lab_trans( + 10_GeV, + MomentumVector(boostLab.getOriginalCS(), {0_eV, momentum(10_GeV, 1_GeV), 0_eV})); + FourMomentum p4lab_long( + 10_GeV, + MomentumVector(boostLab.getOriginalCS(), {momentum(10_GeV, 1_GeV), 0_GeV, 0_eV})); + // boost of transverse momentum + CHECK(boostLab.toCoM(p4lab_trans).getNorm() / 1_GeV == Approx(1)); + CHECK(boostLab.toCoM(p4lab_trans).getTimeLikeComponent() / 1_GeV == Approx(50.99)); + // boost of longitudinal momentum + CHECK(boostLab.toCoM(p4lab_long).getNorm() / 1_GeV == Approx(1)); + CHECK(boostLab.toCoM(p4lab_long).getTimeLikeComponent() / 1_GeV == + Approx(1.24).margin(0.1)); + } } TEST_CASE("rest frame") { diff --git a/tests/framework/testCascade.cpp b/tests/framework/testCascade.cpp index 9c9918729adf17fc8d7f261c81132bab5ae869c7..18732cb1f5faaa646fd9333a89faa1e9e2861973 100644 --- a/tests/framework/testCascade.cpp +++ b/tests/framework/testCascade.cpp @@ -18,8 +18,9 @@ #include <corsika/framework/core/Logging.hpp> #include <corsika/framework/geometry/Point.hpp> -#include <corsika/framework/geometry/RootCoordinateSystem.hpp> #include <corsika/framework/geometry/Vector.hpp> +#include <corsika/framework/geometry/FourVector.hpp> +#include <corsika/framework/geometry/RootCoordinateSystem.hpp> #include <corsika/media/HomogeneousMedium.hpp> #include <corsika/media/NuclearComposition.hpp> @@ -55,8 +56,8 @@ auto make_dummy_env() { Point{env.getCoordinateSystem(), 0_m, 0_m, 0_m}, 1_km * std::numeric_limits<double>::infinity()); - using MyEmptyModel = Empty<IEmpty>; - world->setModelProperties<MyEmptyModel>(); + NuclearComposition const composition({Code::Proton}, {1.}); + world->setModelProperties<TestEnvironmentInterface>(19.2_g / cube(1_cm), composition); universe.addChild(std::move(world)); return env; @@ -86,16 +87,14 @@ public: class ProcessSplit : public InteractionProcess<ProcessSplit> { - int calls_ = 0; - public: - template <typename Particle> - GrammageType getInteractionLength(Particle const&) const { - return 0_g / square(1_cm); + CrossSectionType getCrossSection(Code const, Code const, FourMomentum const&, + FourMomentum const&) const { + return 1_mb; } template <typename TView> - void doInteraction(TView& view) { + void doInteraction(TView& view, Code, Code, FourMomentum const&, FourMomentum const&) { ++calls_; auto vP = view.getProjectile(); const HEPEnergyType Ekin = vP.getKineticEnergy(); @@ -106,17 +105,16 @@ public: } int getCalls() const { return calls_; } + +private: + int calls_ = 0; }; class ProcessCut : public SecondariesProcess<ProcessCut> { - int count_ = 0; - int calls_ = 0; - HEPEnergyType fEcrit; - public: - ProcessCut(HEPEnergyType e) - : fEcrit(e) {} + ProcessCut(HEPEnergyType const e) + : Ecrit_(e) {} template <typename TStack> void doSecondaries(TStack& vS) { @@ -124,7 +122,7 @@ public: auto p = vS.begin(); while (p != vS.end()) { HEPEnergyType E = p.getEnergy(); - if (E < fEcrit) { + if (E < Ecrit_) { p.erase(); count_++; } @@ -136,6 +134,11 @@ public: int getCount() const { return count_; } int getCalls() const { return calls_; } + +private: + int count_ = 0; + int calls_ = 0; + HEPEnergyType Ecrit_; }; TEST_CASE("Cascade", "[Cascade]") { @@ -153,7 +156,7 @@ TEST_CASE("Cascade", "[Cascade]") { StackInspector<TestCascadeStack> stackInspect(100, true, E0); NullModel nullModel; - const HEPEnergyType Ecrit = 85_MeV; + HEPEnergyType const Ecrit = 85_MeV; ProcessSplit split; ProcessCut cut(Ecrit); auto sequence = make_sequence(nullModel, stackInspect, split, cut); @@ -172,7 +175,6 @@ TEST_CASE("Cascade", "[Cascade]") { SECTION("full cascade") { EAS.run(); - CHECK(cut.getCount() == 2048); CHECK(cut.getCalls() == 2047); // final particle is still on stack and not yet deleted CHECK(split.getCalls() == 2047); diff --git a/tests/framework/testCascade.hpp b/tests/framework/testCascade.hpp index cb6e0b5a0c8e06ab7a51fbf6c5a2565760171e97..8c37a22d28e6f1dc380415a20bd2d5bf1dd84288 100644 --- a/tests/framework/testCascade.hpp +++ b/tests/framework/testCascade.hpp @@ -9,14 +9,15 @@ #pragma once #include <corsika/media/Environment.hpp> -#include <corsika/media/IEmpty.hpp> +#include <corsika/media/IMediumModel.hpp> +#include <corsika/media/HomogeneousMedium.hpp> #include <corsika/framework/stack/CombinedStack.hpp> #include <corsika/framework/stack/SecondaryView.hpp> #include <corsika/stack/GeometryNodeStackExtension.hpp> #include <corsika/stack/VectorStack.hpp> -using TestEnvironmentInterface = corsika::IEmpty; +using TestEnvironmentInterface = corsika::HomogeneousMedium<corsika::IMediumModel>; using TestEnvironmentType = corsika::Environment<TestEnvironmentInterface>; template <typename T> diff --git a/tests/framework/testInteractionCounter.cpp b/tests/framework/testInteractionCounter.cpp index 4ea5d49b04f462e43517ed96b3bf6f565a29fba3..50c2397c9e2dd831401f60be0ad99bc6a89b7712 100644 --- a/tests/framework/testInteractionCounter.cpp +++ b/tests/framework/testInteractionCounter.cpp @@ -7,17 +7,11 @@ */ #include <corsika/framework/process/InteractionCounter.hpp> -#include <corsika/media/Environment.hpp> -#include <corsika/media/HomogeneousMedium.hpp> -#include <corsika/media/NuclearComposition.hpp> #include <corsika/framework/geometry/Point.hpp> #include <corsika/framework/geometry/RootCoordinateSystem.hpp> #include <corsika/framework/geometry/Vector.hpp> #include <corsika/framework/core/PhysicalUnits.hpp> -#include <SetupTestStack.hpp> -#include <SetupTestEnvironment.hpp> - #include <catch2/catch.hpp> #include <numeric> @@ -32,37 +26,46 @@ using namespace corsika; const std::string refDataDir = std::string(REFDATADIR); // from cmake struct DummyProcess { - template <typename TParticle> - GrammageType getInteractionLength(TParticle const&) { - return 100_g / 1_cm / 1_cm; + + CrossSectionType getCrossSection(Code const, Code const, FourMomentum const&, + FourMomentum const&) { + return 100_mb; } + template <typename TParticle> - void doInteraction(TParticle&) {} + void doInteraction(TParticle&, Code const, Code const, FourMomentum const&, + FourMomentum const&) {} }; -TEST_CASE("InteractionCounter", "[process]") { +struct DummyOutput { + /* can do nothing */ +}; + +TEST_CASE("InteractionCounter", "process") { logging::set_level(logging::level::info); DummyProcess d; InteractionCounter countedProcess(d); - SECTION("getInteractionLength") { - CHECK(countedProcess.getInteractionLength(nullptr) == 100_g / 1_cm / 1_cm); - } + auto const rootCS = get_root_CoordinateSystem(); + DummyOutput output; - auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Oxygen); - [[maybe_unused]] auto& env_dummy = env; + SECTION("cross section pass-through") { + CHECK(countedProcess.getCrossSection( + Code::Oxygen, Code::Proton, {10_GeV, {rootCS, {0_eV, 0_eV, 0_eV}}}, + {10_GeV, {rootCS, {0_eV, 0_eV, 0_eV}}}) == 100_mb); + } - SECTION("DoInteraction nucleus") { + SECTION("doInteraction nucleus") { unsigned short constexpr A = 14, Z = 7; - auto [stackPtr, secViewPtr] = setup::testing::setup_stack( - get_nucleus_code(A, Z), 105_TeV, (setup::Environment::BaseNodeType* const)nodePtr, - *csPtr); - CHECK(stackPtr->getEntries() == 1); - CHECK(secViewPtr->getEntries() == 0); + Code const pid = get_nucleus_code(A, Z); - countedProcess.doInteraction(*secViewPtr); + countedProcess.doInteraction( + output, pid, Code::Oxygen, + {sqrt(static_pow<2>(105_TeV) + static_pow<2>(get_mass(pid))), + {rootCS, {105_TeV, 0_GeV, 0_GeV}}}, + {Oxygen::mass, {rootCS, {0_eV, 0_eV, 0_eV}}}); auto const& h = countedProcess.getHistogram().labHist(); CHECK(h.at(h.axis(0).index(1'000'070'140), h.axis(1).index(1.05e14)) == 1); @@ -99,14 +102,14 @@ TEST_CASE("InteractionCounter", "[process]") { } } - SECTION("DoInteraction Lambda") { - auto constexpr code = Code::Lambda0; - auto [stackPtr, secViewPtr] = setup::testing::setup_stack( - code, 105_TeV, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); - CHECK(stackPtr->getEntries() == 1); - CHECK(secViewPtr->getEntries() == 0); + SECTION("doInteraction Lambda") { + auto constexpr pid = Code::Lambda0; - countedProcess.doInteraction(*secViewPtr); + countedProcess.doInteraction( + output, pid, Code::Oxygen, + {sqrt(static_pow<2>(105_TeV) + static_pow<2>(get_mass(pid))), + {rootCS, {105_TeV, 0_GeV, 0_GeV}}}, + {Oxygen::mass, {rootCS, {0_eV, 0_eV, 0_eV}}}); auto const& h = countedProcess.getHistogram().labHist(); CHECK(h.at(h.axis(0).index(3122), h.axis(1).index(1.05e14)) == 1); diff --git a/tests/framework/testProcessSequence.cpp b/tests/framework/testProcessSequence.cpp index cf9dc523f4a3fe571e3d3323b957534ac790b285..9b6dce5056a78abe10529b96f75a32e521b31055 100644 --- a/tests/framework/testProcessSequence.cpp +++ b/tests/framework/testProcessSequence.cpp @@ -9,10 +9,15 @@ #include <corsika/framework/process/ProcessSequence.hpp> #include <corsika/framework/process/SwitchProcessSequence.hpp> -#include <corsika/framework/core/PhysicalUnits.hpp> #include <corsika/framework/process/ProcessTraits.hpp> #include <corsika/framework/process/ContinuousProcessStepLength.hpp> +#include <corsika/framework/core/PhysicalUnits.hpp> + +#include <corsika/framework/utility/COMBoost.hpp> + +#include <corsika/media/NuclearComposition.hpp> + #include <catch2/catch.hpp> #include <array> @@ -30,6 +35,12 @@ using namespace corsika; using namespace std; +struct DummyRNG { + int max() const { return 10; } + int min() const { return 0; } + double operator()() const { return 0.5; } +}; + static int const nData = 10; // DummyNode is only needed for BoundaryCrossingProcess @@ -39,15 +50,21 @@ struct DummyNode { int data_ = 0; }; -// The stack is non-existent for this example -struct DummyStack {}; - // our data object (particle) is a simple arrary of doubles struct DummyData { double data_[nData] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; typedef DummyNode node_type; // for BoundaryCrossingProcess + Code getPID() const { return Code::Proton; } + MomentumVector getMomentum() const { + // only need the coordinate system + return MomentumVector{get_root_CoordinateSystem(), 0_eV, 0_eV, 0_eV}; + } + HEPEnergyType getEnergy() const { return 10_GeV; } }; +// The stack is non-existent for this example +struct DummyStack {}; + // there is no real trajectory/track struct DummyTrajectory {}; @@ -58,6 +75,7 @@ struct DummyView { : p_(p) {} DummyData& p_; DummyData& parent() { return p_; } + // this is only needed because of PROPOSAL interface right now: }; int globalCount = 0; // simple counter @@ -193,14 +211,15 @@ public: } template <typename TView> - void doInteraction(TView& v) const { + void doInteraction(TView& v, Code const, Code const, FourMomentum const&, + FourMomentum const&) const { checkInteract |= 1; for (int i = 0; i < nData; ++i) v.parent().data_[i] += 1 + i; } - template <typename TParticle> - GrammageType getInteractionLength(TParticle&) const { - return 10_g / square(1_cm); + CrossSectionType getCrossSection(Code const, Code const, FourMomentum const&, + FourMomentum const&) const { + return 10_mb; } private: @@ -219,15 +238,17 @@ public: } template <typename TView> - void doInteraction(TView& v) const { + void doInteraction(TView& v, Code const, Code const, FourMomentum const&, + FourMomentum const&) const { checkInteract |= 2; for (int i = 0; i < nData; ++i) v.parent().data_[i] /= 1.1; CORSIKA_LOG_DEBUG("Process2::doInteraction"); } - template <typename Particle> - GrammageType getInteractionLength(Particle&) const { - CORSIKA_LOG_DEBUG("Process2::GetInteractionLength"); - return 20_g / (1_cm * 1_cm); + + CrossSectionType getCrossSection(Code const, Code const, FourMomentum const&, + FourMomentum const&) const { + CORSIKA_LOG_DEBUG("Process2::getCrossSection"); + return 20_mb; } private: @@ -246,15 +267,17 @@ public: } template <typename TView> - void doInteraction(TView& v) const { + void doInteraction(TView& v, Code const, Code const, FourMomentum const&, + FourMomentum const&) const { checkInteract |= 4; for (int i = 0; i < nData; ++i) v.parent().data_[i] *= 1.01; CORSIKA_LOG_DEBUG("Process3::doInteraction"); } - template <typename Particle> - GrammageType getInteractionLength(Particle&) const { - CORSIKA_LOG_DEBUG("Process3::GetInteractionLength"); - return 30_g / (1_cm * 1_cm); + + CrossSectionType getCrossSection(Code const, Code const, FourMomentum const&, + FourMomentum const&) const { + CORSIKA_LOG_DEBUG("Process3::getCrossSection"); + return 30_mb; } private: @@ -280,7 +303,8 @@ public: return ProcessReturn::Ok; } template <typename TView> - void doInteraction(TView&) const { + void doInteraction(TView&, Code const, Code const, FourMomentum const&, + FourMomentum const&) const { checkInteract |= 8; } @@ -430,27 +454,6 @@ TEST_CASE("ProcessSequence General", "ProcessSequence") { sequence2_rv.getProcess2().getProcess2())>); // Process3 } - SECTION("interaction length") { - globalCount = 0; - ContinuousProcess1 cp1(0, 1_m); - Process2 m2(1); - Process3 m3(2); - - DummyData particle; - - auto sequence2 = make_sequence(cp1, m2, m3); - GrammageType const tot = sequence2.getInteractionLength(particle); - InverseGrammageType const tot_inv = sequence2.getInverseInteractionLength(particle); - CORSIKA_LOG_DEBUG( - "lambda_tot={}" - "; lambda_tot_inv={}", - tot, tot_inv); - - CHECK(tot / 1_g * square(1_cm) == 12); - CHECK(tot_inv * 1_g / square(1_cm) == 1. / 12); - globalCount = 0; - } - SECTION("lifetime") { globalCount = 0; ContinuousProcess1 cp1(0, 1_m); @@ -597,6 +600,8 @@ TEST_CASE("SwitchProcessSequence", "ProcessSequence") { logging::set_level(logging::level::info); + CoordinateSystemPtr rootCS = get_root_CoordinateSystem(); + /** * In this example switching is done only by "data_[0]>0", where * data in an arrray of doubles, DummyData. @@ -614,12 +619,20 @@ TEST_CASE("SwitchProcessSequence", "ProcessSequence") { auto sec1 = Secondaries1(); auto sec2 = Secondaries2(); - auto sequence1 = make_sequence(Process1(0), cp2, Decay1(0), sec1, Boundary1(1.0)); - auto sequence2 = make_sequence(cp3, Process2(0), Boundary1(-1.0), Decay2(0), sec2); + auto sequence1 = + make_sequence(Process1(0), cp2, Decay1(0), sec1, Boundary1(1.0)); // 10 mb + auto sequence2 = + make_sequence(cp3, Process2(0), Boundary1(-1.0), Decay2(0), sec2); // 20 mb - auto sequence3 = make_sequence(cp1, Process3(0), + auto sequence3 = make_sequence(cp1, Process3(0), // 30 mb SwitchProcessSequence(select1, sequence1, sequence2)); + // it is even more typical to have just one sub-process inside the branches of + // SwitchProcessSequence + auto sequence3_short = + make_sequence(cp1, Process3(0), // 30 mb + SwitchProcessSequence(select1, Process1(0), Process2(0))); + auto sequence4 = make_sequence(cp1, Boundary1(2.0), Process3(0), SwitchProcessSequence(select1, sequence1, Boundary1(-1.0))); @@ -686,24 +699,28 @@ TEST_CASE("SwitchProcessSequence", "ProcessSequence") { CHECK(checkCont == 0b101); CHECK(checkSec == 0); - // 1/(30g/cm2) is Process3 - InverseGrammageType lambda_select = .9 / 30. * square(1_cm) / 1_g; - InverseTimeType time_select = 0.1 / second; + // 30_mb is Process3 + CrossSectionType cx_select = .9 * 30_mb; + InverseTimeType time_select = 0.1 / second; // for decay checkDecay = 0; checkInteract = 0; checkSec = 0; checkCont = 0; particle.data_[0] = 100; // data positive --> sequence1 - sequence3.selectInteraction(view, lambda_select); + + DummyRNG rng; + FourMomentum const projectileP4{10_GeV, {rootCS, {0_eV, 0_eV, 0_eV}}}; + NuclearComposition const noComposition({Code::Nitrogen}, {1}); + sequence3.selectInteraction(view, projectileP4, noComposition, rng, cx_select); sequence3.selectDecay(view, time_select); CHECK(checkInteract == 0b100); // this is Process3 CHECK(checkDecay == 0b001); // this is Decay1 CHECK(checkCont == 0); CHECK(checkSec == 0); - lambda_select = 1.01 / 30. * square(1_cm) / 1_g; + cx_select = 1.01 * 30_mb; checkInteract = 0; - sequence3.selectInteraction(view, lambda_select); + sequence3.selectInteraction(view, projectileP4, noComposition, rng, cx_select); CHECK(checkInteract == 0b001); // this is Process1 checkDecay = 0; @@ -711,7 +728,7 @@ TEST_CASE("SwitchProcessSequence", "ProcessSequence") { checkSec = 0; checkCont = 0; particle.data_[0] = -100; // data negative --> sequence2 - sequence3.selectInteraction(view, lambda_select); + sequence3.selectInteraction(view, projectileP4, noComposition, rng, cx_select); sequence3.selectDecay(view, time_select); CHECK(checkInteract == 0b010); // this is Process2 CHECK(checkDecay == 0b010); // this is Decay2 @@ -739,7 +756,7 @@ TEST_CASE("SwitchProcessSequence", "ProcessSequence") { checkSec = 0; checkCont = 0; particle.data_[0] = -100; // data negative --> sequence1 - sequence4.selectInteraction(view, lambda_select); + sequence4.selectInteraction(view, projectileP4, noComposition, rng, cx_select); sequence4.doSecondaries(view); sequence4.selectDecay(view, time_select); sequence4.doSecondaries(view); @@ -748,15 +765,96 @@ TEST_CASE("SwitchProcessSequence", "ProcessSequence") { CHECK(checkCont == 0); CHECK(checkSec == 0); - // check that large "select" value will correctly ignore the call - lambda_select = 1e5 * square(1_cm) / 1_g; - time_select = 1e5 / second; - checkDecay = 0; - checkInteract = 0; - sequence3.selectInteraction(view, lambda_select); - sequence3.selectDecay(view, time_select); - CHECK(checkInteract == 0); - CHECK(checkDecay == 0); + // now check sequence3, which contains a SwitchProcessSequence that contains two + // longer sequences in each branch. + { + // check that large "select" value will correctly ignore the call + cx_select = 1e5_mb; + time_select = 1e5 / second; + checkDecay = 0; + checkInteract = 0; + sequence3.selectInteraction(view, projectileP4, noComposition, rng, cx_select); + sequence3.selectDecay(view, time_select); + CHECK(checkInteract == 0); + CHECK(checkDecay == 0); + + // for a small cx_select selection must be sucessful + cx_select = 28_mb; // -> Process3 + checkInteract = 0; + particle.data_[0] = -100; // data negative --> sequence2 + CHECK(sequence3.getCrossSection(particle, Code::Oxygen, + {Oxygen::mass, {rootCS, {0_eV, 0_eV, 0_eV}}}) / + 1_mb == + Approx(50.)); + sequence3.selectInteraction(view, projectileP4, noComposition, rng, cx_select); + CHECK(checkInteract == 4); // 2^3 + + particle.data_[0] = 100; // data positive --> sequence1 + checkInteract = 0; + CHECK(sequence3.getCrossSection(particle, Code::Oxygen, + {Oxygen::mass, {rootCS, {0_eV, 0_eV, 0_eV}}}) / + 1_mb == + Approx(40.)); + sequence3.selectInteraction(view, projectileP4, noComposition, rng, cx_select); + CHECK(checkInteract == 4); // 2^3 + + cx_select = 32_mb; // -> Process2 or Process1 + checkInteract = 0; + particle.data_[0] = -100; // data negative --> Process2 + sequence3.selectInteraction(view, projectileP4, noComposition, rng, cx_select); + CHECK(checkInteract == 2); // 2^2 + + particle.data_[0] = 100; // data positive --> Process1 + checkInteract = 0; + sequence3.selectInteraction(view, projectileP4, noComposition, rng, cx_select); + CHECK(checkInteract == 1); // 2^1 + } + + // now check sequence3, which contains a SwitchProcessSequence that contains just two + // bare InteractionProcess-es in each branch. + { + // check that large "select" value will correctly ignore the call + cx_select = 1e5_mb; + checkInteract = 0; + sequence3_short.selectInteraction(view, projectileP4, noComposition, rng, + cx_select); + CHECK(checkInteract == 0); + + // for a small cx_select selection must be sucessful + cx_select = 28_mb; // -> Process3 + checkInteract = 0; + particle.data_[0] = -100; // data negative --> sequence2 + CHECK(sequence3_short.getCrossSection( + particle, Code::Oxygen, {Oxygen::mass, {rootCS, {0_eV, 0_eV, 0_eV}}}) / + 1_mb == + Approx(50.)); + sequence3_short.selectInteraction(view, projectileP4, noComposition, rng, + cx_select); + CHECK(checkInteract == 4); // 2^3 + + particle.data_[0] = 100; // data positive --> sequence1 + checkInteract = 0; + CHECK(sequence3_short.getCrossSection( + particle, Code::Oxygen, {Oxygen::mass, {rootCS, {0_eV, 0_eV, 0_eV}}}) / + 1_mb == + Approx(40.)); + sequence3_short.selectInteraction(view, projectileP4, noComposition, rng, + cx_select); + CHECK(checkInteract == 4); // 2^3 + + cx_select = 32_mb; // -> Process2 or Process1 + checkInteract = 0; + particle.data_[0] = -100; // data negative --> Process2 + sequence3_short.selectInteraction(view, projectileP4, noComposition, rng, + cx_select); + CHECK(checkInteract == 2); // 2^2 + + particle.data_[0] = 100; // data positive --> Process1 + checkInteract = 0; + sequence3_short.selectInteraction(view, projectileP4, noComposition, rng, + cx_select); + CHECK(checkInteract == 1); // 2^1 + } } SECTION("Check SecondariesProcesses in SwitchProcessSequence") { diff --git a/tests/media/CMakeLists.txt b/tests/media/CMakeLists.txt index ffcbe11d185219c6c35e34d91e4a4dcf48b75794..c1265193c49d0f6f1e1990718f7d2615672f8bed 100644 --- a/tests/media/CMakeLists.txt +++ b/tests/media/CMakeLists.txt @@ -1,5 +1,6 @@ set (test_media_sources TestMain.cpp + testNuclearComposition.cpp testEnvironment.cpp testShowerAxis.cpp testMedium.cpp diff --git a/tests/media/testEnvironment.cpp b/tests/media/testEnvironment.cpp index 43476644e38aa44b987a0386c2d123a7e630cf67..81c1ac75701baaf8c670ddb7525d3d41dfc2d1f5 100644 --- a/tests/media/testEnvironment.cpp +++ b/tests/media/testEnvironment.cpp @@ -82,9 +82,6 @@ TEST_CASE("HomogeneousMedium") { NuclearComposition const protonComposition(std::vector<Code>{Code::Proton}, {1.}); HomogeneousMedium<IMediumModel> const medium(19.2_g / cube(1_cm), protonComposition); - CHECK(protonComposition.getFractions() == std::vector<float>{1.}); - CHECK(protonComposition.getComponents() == std::vector<Code>{Code::Proton}); - CHECK_THROWS(NuclearComposition({Code::Proton}, {1.1})); CHECK_THROWS(NuclearComposition({Code::Proton}, {0.99})); } @@ -104,7 +101,7 @@ TEST_CASE("FlatExponential") { LengthType const length = 2_m; TimeType const tEnd = length / speed; - CHECK(medium.getNuclearComposition().getFractions() == std::vector<float>{1.}); + CHECK(medium.getNuclearComposition().getFractions() == std::vector<double>{1.}); CHECK(medium.getNuclearComposition().getComponents() == std::vector<Code>{Code::Proton}); @@ -514,7 +511,7 @@ TEST_CASE("InhomogeneousMedium") { LengthType const length = tEnd * speed; - NuclearComposition const composition{{Code::Proton}, {1.f}}; + NuclearComposition const composition{{Code::Proton}, {1.}}; InhomogeneousMedium<IMediumModel, decltype(rho)> const inhMedium(composition, rho); CORSIKA_LOG_INFO("test={} l={} {} {}", rho.getIntegrateGrammage(trajectory), length, diff --git a/tests/media/testMagneticField.cpp b/tests/media/testMagneticField.cpp index 6e9f347b8c7447cf227afdd191acc5362c7e1283..083ccf56881426aad23f7201cdb9a520aecb796a 100644 --- a/tests/media/testMagneticField.cpp +++ b/tests/media/testMagneticField.cpp @@ -34,8 +34,7 @@ TEST_CASE("UniformMagneticField w/ Homogeneous Medium") { using AtmModel = UniformMagneticField<HomogeneousMedium<IModelInterface>>; // the composition we use for the homogenous medium - NuclearComposition const protonComposition(std::vector<Code>{Code::Proton}, - std::vector<float>{1.f}); + NuclearComposition const protonComposition({Code::Proton}, {1.}); // create a magnetic field vector Vector B0(gCS, 0_T, 0_T, 0_T); diff --git a/tests/media/testMedium.cpp b/tests/media/testMedium.cpp index fb55aaa59aa28d1aef411a4f31b143cdd50dd21a..2cbf6a7bb4ab55962ff8970cace93aacde7ac007 100644 --- a/tests/media/testMedium.cpp +++ b/tests/media/testMedium.cpp @@ -58,8 +58,7 @@ TEST_CASE("MediumPropertyModel w/ Homogeneous") { const auto density{19.2_g / cube(1_cm)}; // the composition we use for the homogenous medium - NuclearComposition const protonComposition(std::vector<Code>{Code::Proton}, - std::vector<float>{1.f}); + NuclearComposition const protonComposition({Code::Proton}, {1.}); // the refrative index that we use const Medium type = corsika::Medium::AirDry1Atm; diff --git a/tests/media/testNuclearComposition.cpp b/tests/media/testNuclearComposition.cpp new file mode 100644 index 0000000000000000000000000000000000000000..79b019dc94f435c461eb051627c6665296717796 --- /dev/null +++ b/tests/media/testNuclearComposition.cpp @@ -0,0 +1,69 @@ +/* + * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu + * + * This software is distributed under the terms of the GNU General Public + * Licence version 3 (GPL Version 3). See file LICENSE for a full version of + * the license. + */ + +#include <corsika/framework/core/ParticleProperties.hpp> +#include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/framework/core/Logging.hpp> +#include <corsika/media/NuclearComposition.hpp> + +#include <catch2/catch.hpp> + +using namespace corsika; + +struct DummyRNG { + double v_; + DummyRNG(double v) + : v_(v) {} + int max() const { return 10; } + int min() const { return 0; } + double operator()() const { return v_; } +}; + +TEST_CASE("NuclearComposition") { + + logging::set_level(logging::level::info); + + // incompatible input: wrong vectors + CHECK_THROWS( + NuclearComposition({Code::Oxygen, Code::Carbon}, {0.20, 0.05, 1 - 0.20 - 0.05})); + // incompatible input: wrong fractions + CHECK_THROWS( + NuclearComposition({Code::Oxygen, Code::Carbon}, {0.21, 0.05, 1 - 0.20 - 0.05})); + // incompatible input: wrong fractions + CHECK_THROWS( + NuclearComposition({Code::Oxygen, Code::Carbon}, {0.19, 0.05, 1 - 0.20 - 0.05})); + + NuclearComposition const testComposition({Code::Oxygen, Code::Carbon, Code::Nitrogen}, + {0.20, 0.05, 1 - 0.20 - 0.05}); + + CHECK(testComposition.getSize() == 3); + CHECK(testComposition.getFractions() == std::vector<double>{0.2, 0.05, 1 - 0.2 - 0.05}); + CHECK(testComposition.getComponents() == + std::vector<Code>{Code::Oxygen, Code::Carbon, Code::Nitrogen}); + + CHECK(testComposition.getHash() == + 18183071370474897160U); // we need a stable hasing algorithm + CHECK(testComposition.getAverageMassNumber() == 14.3); + + CHECK(testComposition.getWeighted([](Code) -> double { return 1; }) == + std::vector<double>{0.2, 0.05, 1 - 0.2 - 0.05}); + + std::vector<CrossSectionType> const testCX = + testComposition.getWeighted([](Code) -> CrossSectionType { return 1_mb; }); + std::vector<CrossSectionType> const checkCX{0.2_mb, 0.05_mb, 1_mb - 0.2_mb - 0.05_mb}; + for (auto i1 = testCX.begin(), i2 = checkCX.begin(); i1 != testCX.end(); ++i1, ++i2) { + CHECK(*i1 / 1_mb == Approx(*i2 / 1_mb)); + } + + CHECK(testComposition.getWeightedSum([](Code) -> double { return 1; }) == 1); + + CHECK(testComposition.getWeightedSum([](Code) -> CrossSectionType { return 1_mb; }) == + 1_mb); + + CHECK(testComposition.sampleTarget(testCX, DummyRNG(0.1)) == Code::Oxygen); +} diff --git a/tests/media/testRefractiveIndex.cpp b/tests/media/testRefractiveIndex.cpp index 62422f7a8c8d76da32f25a0972b7391ee7fb3ccf..53c9a694c7c6c3f4f2a05974597856f47bb36140 100644 --- a/tests/media/testRefractiveIndex.cpp +++ b/tests/media/testRefractiveIndex.cpp @@ -42,8 +42,7 @@ TEST_CASE("UniformRefractiveIndex w/ Homogeneous") { const auto density{19.2_g / cube(1_cm)}; // the composition we use for the homogenous medium - NuclearComposition const protonComposition(std::vector<Code>{Code::Proton}, - std::vector<float>{1.f}); + NuclearComposition const protonComposition({Code::Proton}, {1.}); // the refrative index that we use const double n{1.000327}; @@ -113,8 +112,7 @@ TEST_CASE("ExponentialRefractiveIndex w/ Homogeneous medium") { const auto density{19.2_g / cube(1_cm)}; // the composition we use for the homogenous medium - NuclearComposition const protonComposition(std::vector<Code>{Code::Proton}, - std::vector<float>{1.f}); + NuclearComposition const protonComposition({Code::Proton}, {1.}); // a new refractive index const double n0{2}; diff --git a/tests/media/testShowerAxis.cpp b/tests/media/testShowerAxis.cpp index 9378d9b41fcab19f9bf3a1b1547c3f9cdb59e6b4..d8285e322749d9269e0f8ff7b37bd9c4a733bce0 100644 --- a/tests/media/testShowerAxis.cpp +++ b/tests/media/testShowerAxis.cpp @@ -36,8 +36,7 @@ auto setupEnvironment(Code vTargetCode) { using MyHomogeneousModel = HomogeneousMedium<IMediumModel>; theMedium->setModelProperties<MyHomogeneousModel>( - density, - NuclearComposition(std::vector<Code>{vTargetCode}, std::vector<float>{1.})); + density, NuclearComposition({vTargetCode}, {1.})); auto const* nodePtr = theMedium.get(); universe.addChild(std::move(theMedium)); diff --git a/tests/modules/CMakeLists.txt b/tests/modules/CMakeLists.txt index 121eb11f05324c229696fde27aef25aa064b39ac..a4e1c1b63c1b86df66e347d3ab28ca294985e97e 100644 --- a/tests/modules/CMakeLists.txt +++ b/tests/modules/CMakeLists.txt @@ -2,13 +2,13 @@ set (test_modules_sources TestMain.cpp testStackInspector.cpp testTracking.cpp - # testExecTime.cpp --> needs to be fixed, see #326 + ## testExecTime.cpp --> needs to be fixed, see #326 testObservationPlane.cpp testQGSJetII.cpp testPythia8.cpp testUrQMD.cpp testCONEX.cpp - # testOnShellCheck.cpp + ## testOnShellCheck.cpp testParticleCut.cpp testSibyll.cpp testEpos.cpp diff --git a/tests/modules/testCONEX.cpp b/tests/modules/testCONEX.cpp index 16539ba56ee241c6afed50bd3748a7ab941314c8..d04f3ac808c051defdd4de6c4ca6bafa9d59adad 100644 --- a/tests/modules/testCONEX.cpp +++ b/tests/modules/testCONEX.cpp @@ -100,7 +100,7 @@ TEST_CASE("CONEXSourceCut") { // need to initialize Sibyll, done in constructor: corsika::sibyll::Interaction sibyll; - [[maybe_unused]] corsika::sibyll::NuclearInteraction sibyllNuc(sibyll, env); + [[maybe_unused]] corsika::sibyll::NuclearInteractionModel sibyllNuc(sibyll, env); CONEXhybrid conex(center, showerAxis, t, injectionHeight, E0, get_PDG(Code::Proton)); conex.initCascadeEquations(); diff --git a/tests/modules/testEpos.cpp b/tests/modules/testEpos.cpp index 3eab3eaafb7ddf1cfc169798dddd9cc7120d80ec..d0f13d94fa941ae6931b9874b43d208240d9b5ad 100644 --- a/tests/modules/testEpos.cpp +++ b/tests/modules/testEpos.cpp @@ -29,7 +29,7 @@ using namespace corsika; using namespace corsika::epos; -TEST_CASE("epos", "module,process") { +TEST_CASE("EposBasics", "module,process") { logging::set_level(logging::level::trace); @@ -71,6 +71,7 @@ TEST_CASE("epos", "module,process") { SECTION("epos mass") { CHECK_FALSE(corsika::epos::getEposMass(Code::Electron) / 1_GeV == Approx(0)); + CHECK_THROWS(corsika::epos::getEposMass(Code::Unknown)); } /* @@ -88,6 +89,7 @@ TEST_CASE("epos", "module,process") { CHECK(p == convert_from_PDG(getEposPDGId(p))); } } + CHECK_THROWS(getEposPDGId(Code::Oxygen)); } } @@ -118,138 +120,179 @@ auto sqs2elab(HEPEnergyType const sqs, HEPEnergyType const ma, HEPEnergyType con return (sqs * sqs - ma * ma - mb * mb) / 2. / mb; } -TEST_CASE("EposInterface", "modules") { +TEST_CASE("Epos", "modules") { logging::set_level(logging::level::trace); + RNGManager<>::getInstance().registerRandomStream("epos"); + InteractionModel model; + auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Oxygen); auto const& cs = *csPtr; [[maybe_unused]] auto const& env_dummy = env; - RNGManager<>::getInstance().registerRandomStream("epos"); - SECTION("InteractionInterface - random number") { auto const rndm = ::epos::rangen_(); CHECK(rndm > 0); CHECK(rndm < 1); } - SECTION("InteractionInterface - valid targets") { + SECTION("InteractionInterface - isValid") { - Interaction model; - CHECK_FALSE(model.isValidTarget(Code::Electron)); - CHECK(model.isValidTarget(Code::Hydrogen)); - CHECK(model.isValidTarget(Code::Helium)); - CHECK_FALSE(model.isValidTarget(Code::Iron)); - CHECK(model.isValidTarget(Code::Oxygen)); + CHECK_FALSE(model.isValid(Code::Proton, Code::Electron, 100_GeV)); + CHECK(model.isValid(Code::Proton, Code::Hydrogen, 100_GeV)); + CHECK(model.isValid(Code::Proton, Code::Helium, 100_GeV)); + CHECK_FALSE(model.isValid(Code::Proton, Code::Iron, 100_GeV)); + CHECK(model.isValid(Code::Proton, Code::Oxygen, 100_GeV)); + } + + SECTION("InteractionInterface - getCrossSectionInelEla") { // hydrogen target == proton target == neutron target - auto const [xs_prod_pp, xs_ela_pp] = - model.getCrossSectionLab(Code::Proton, 1, 1, Code::Proton, 1, 1, 100_GeV); - auto const [xs_prod_pn, xs_ela_pn] = - model.getCrossSectionLab(Code::Proton, 1, 1, Code::Neutron, 1, 0, 100_GeV); - auto const [xs_prod_pHydrogen, xs_ela_pHydrogen] = - model.getCrossSectionLab(Code::Proton, 1, 1, Code::Hydrogen, 1, 1, 100_GeV); + auto const [xs_prod_pp, xs_ela_pp] = model.getCrossSectionInelEla( + Code::Proton, Code::Proton, + {sqrt(static_pow<2>(100_GeV) + static_pow<2>(Proton::mass)), + {cs, 100_GeV, 0_GeV, 0_GeV}}, + {Proton::mass, {cs, 0_GeV, 0_GeV, 0_GeV}}); + + auto const [xs_prod_pn, xs_ela_pn] = model.getCrossSectionInelEla( + Code::Proton, Code::Neutron, + {sqrt(static_pow<2>(100_GeV) + static_pow<2>(Proton::mass)), + {cs, 100_GeV, 0_GeV, 0_GeV}}, + {Neutron::mass, {cs, 0_GeV, 0_GeV, 0_GeV}}); + + auto const [xs_prod_pHydrogen, xs_ela_pHydrogen] = model.getCrossSectionInelEla( + Code::Proton, Code::Hydrogen, + {sqrt(static_pow<2>(100_GeV) + static_pow<2>(Proton::mass)), + {cs, 100_GeV, 0_GeV, 0_GeV}}, + {Hydrogen::mass, {cs, 0_GeV, 0_GeV, 0_GeV}}); + CHECK(xs_prod_pp == xs_prod_pHydrogen); CHECK(xs_prod_pp == xs_prod_pn); CHECK(xs_ela_pp == xs_ela_pHydrogen); CHECK(xs_ela_pn == xs_ela_pHydrogen); - } - SECTION("InteractionInterface - hadron cross sections") { + // invalid system + auto const [xs_prod_0, xs_ela_0] = model.getCrossSectionInelEla( + Code::Electron, Code::Electron, + {sqrt(static_pow<2>(100_GeV) + static_pow<2>(Electron::mass)), + {cs, 100_GeV, 0_GeV, 0_GeV}}, + {Electron::mass, {cs, 0_GeV, 0_GeV, 0_GeV}}); + CHECK(xs_prod_0 / 1_mb == Approx(0)); + CHECK(xs_ela_0 / 1_mb == Approx(0)); + } - Interaction model; + SECTION("InteractionModelInterface - hadron cross sections") { // p-p at 7TeV around 70mb according to LHC - auto const [xs_prod, xs_ela] = - model.getCrossSectionLab(Code::Proton, 1, 1, Code::Proton, 1, 1, - sqs2elab(7_TeV, Proton::mass, Proton::mass)); + auto const xs_prod = model.getCrossSection( + Code::Proton, Code::Proton, + {3.5_TeV, + {cs, sqrt(static_pow<2>(3.5_TeV) - static_pow<2>(Proton::mass)), 0_GeV, 0_GeV}}, + {3.5_TeV, + {cs, -sqrt(static_pow<2>(3.5_TeV) - static_pow<2>(Proton::mass)), 0_GeV, + 0_GeV}}); CHECK(xs_prod / 1_mb == Approx(70.7).margin(2.1)); - { [[maybe_unused]] auto const& dum_xs = xs_ela; } // pi-n at 7TeV - auto const [xs_prod1, xs_ela1] = - model.getCrossSectionLab(Code::PiPlus, 0, 0, Code::Neutron, 1, 0, - sqs2elab(7_TeV, PiPlus::mass, Neutron::mass)); + auto const xs_prod1 = model.getCrossSection( + Code::PiPlus, Code::Neutron, + {3.5_TeV, + {cs, sqrt(static_pow<2>(3.5_TeV) - static_pow<2>(PiPlus::mass)), 0_GeV, 0_GeV}}, + {3.5_TeV, + {cs, -sqrt(static_pow<2>(3.5_TeV) - static_pow<2>(Neutron::mass)), 0_GeV, + 0_GeV}}); CHECK(xs_prod1 / 1_mb == Approx(52.7).margin(2.1)); - { [[maybe_unused]] auto const& dum_xs = xs_ela1; } // k-p at 7TeV - auto const [xs_prod2, xs_ela2] = - model.getCrossSectionLab(Code::KPlus, 0, 0, Code::Proton, 1, 1, - sqs2elab(7_TeV, KPlus::mass, Proton::mass)); + auto const xs_prod2 = model.getCrossSection( + Code::KPlus, Code::Proton, + {3.5_TeV, + {cs, sqrt(static_pow<2>(3.5_TeV) - static_pow<2>(KPlus::mass)), 0_GeV, 0_GeV}}, + {3.5_TeV, + {cs, -sqrt(static_pow<2>(3.5_TeV) - static_pow<2>(Proton::mass)), 0_GeV, + 0_GeV}}); CHECK(xs_prod2 / 1_mb == Approx(45.7).margin(2.1)); - { [[maybe_unused]] auto const& dum_xs = xs_ela2; } } SECTION("InteractionInterface - nuclear cross sections") { - Interaction model; - - auto const [xs_prod, xs_ela] = model.getCrossSectionLab( - Code::Proton, 1, 1, Code::Oxygen, Oxygen::nucleus_A, Oxygen::nucleus_Z, 100_GeV); + auto const xs_prod = model.getCrossSection( + Code::Proton, Code::Oxygen, + {100_GeV, + {cs, sqrt(static_pow<2>(100_GeV) - static_pow<2>(Proton::mass)), 0_GeV, 0_GeV}}, + {Oxygen::mass, {cs, 0_GeV, 0_GeV, 0_GeV}}); CHECK(xs_prod / 1_mb == Approx(287.0).margin(5.1)); - { [[maybe_unused]] auto const& dum_xs = xs_ela; } - auto const [xs_prod2, xs_ela2] = model.getCrossSectionLab( - Code::Nitrogen, Nitrogen::nucleus_A, Nitrogen::nucleus_Z, Code::Oxygen, - Oxygen::nucleus_A, Oxygen::nucleus_Z, 400_GeV); + auto const xs_prod2 = model.getCrossSection( + Code::Nitrogen, Code::Oxygen, + {400_GeV, + {cs, sqrt(static_pow<2>(400_GeV) - static_pow<2>(Nitrogen::mass)), 0_GeV, + 0_GeV}}, + {Oxygen::mass, {cs, 0_GeV, 0_GeV, 0_GeV}}); CHECK(xs_prod2 / 1_mb == Approx(1076.7).margin(3.1)); - { [[maybe_unused]] auto const& dum_xs = xs_ela2; } - - // nuclear stack extension, particle "Nucleus" - auto const [xs_prod3, xs_ela3] = model.getCrossSectionLab( - Code::Nucleus, Nitrogen::nucleus_A, Nitrogen::nucleus_Z, Code::Oxygen, - Oxygen::nucleus_A, Oxygen::nucleus_Z, 400_GeV); - CHECK(xs_prod2 / xs_prod3 == 1); - { [[maybe_unused]] auto const& dum_xs = xs_ela3; } - } - - SECTION("InteractionInterface - low energy") { - - const HEPEnergyType P0 = 60_GeV; - auto [stack, viewPtr] = setup::testing::setup_stack( - Code::Proton, P0, (setup::Environment::BaseNodeType* const)nodePtr, cs); - MomentumVector plab = - MomentumVector(cs, {P0, 0_eV, 0_eV}); // this is secret knowledge about setupStack - setup::StackView& view = *viewPtr; - - auto particle = stack->first(); - - Interaction model; - model.doInteraction(view); - - auto const pSum = sumMomentum(view, cs); - - // this is not physics validation - CHECK(pSum.getComponents(cs).getX() / P0 == Approx(1).margin(0.05)); - CHECK(pSum.getComponents(cs).getY() / 1_GeV == Approx(0).margin(.5)); - CHECK(pSum.getComponents(cs).getZ() / 1_GeV == Approx(0).margin(.5)); - - CHECK((pSum - plab).getNorm() / 1_GeV == - Approx(0).margin(plab.getNorm() * 0.05 / 1_GeV)); - CHECK(pSum.getNorm() / P0 == Approx(1).margin(0.05)); - - [[maybe_unused]] const GrammageType length = model.getInteractionLength(particle); - CHECK(length / 1_g * 1_cm * 1_cm == Approx(93.3).margin(0.1)); } - SECTION("InteractionInterface - nuclear projectile") { - - const HEPEnergyType P0 = 10_TeV; + /* + SECTION("InteractionInterface - invalid") { + Code const pid = Code::Electron; + HEPEnergyType const P0 = 10_TeV; + auto [stack, viewPtr] = setup::testing::setup_stack( + pid, P0, (setup::Environment::BaseNodeType* const)nodePtr, cs); + setup::StackView& view = *viewPtr; + CHECK_THROWS(model.doInteraction( + view, pid, Code::Oxygen, + {sqrt(static_pow<2>(P0) + static_pow<2>(get_mass(pid))), {cs, P0, 0_GeV, + 0_GeV}}, {Oxygen::mass, {cs, 0_GeV, 0_GeV, 0_GeV}})); + } + */ + /* + SECTION("InteractionInterface - nuclear projectile") { + + HEPEnergyType const P0 = 10_TeV; + Code const pid = get_nucleus_code(40, 20); + auto [stack, viewPtr] = setup::testing::setup_stack( + pid, P0, (setup::Environment::BaseNodeType* const)nodePtr, cs); + MomentumVector plab = + MomentumVector(cs, {P0, 0_eV, 0_eV}); // this is secret knowledge about + setupStack setup::StackView& view = *viewPtr; + + // @todo This is very obscure since it fails for -O2, but for both clang and gcc ??? + model.doInteraction(view, pid, Code::Oxygen, + {sqrt(static_pow<2>(P0) + static_pow<2>(get_mass(pid))), plab}, + {Oxygen::mass, {cs, 0_GeV, 0_GeV, 0_GeV}}); + + auto const pSum = sumMomentum(view, cs); + + CHECK(pSum.getComponents(cs).getX() / P0 == Approx(1).margin(0.05)); + CHECK(pSum.getComponents(cs).getY() / 1_GeV == + Approx(0).margin(0.5)); // this is not physics validation + CHECK(pSum.getComponents(cs).getZ() / 1_GeV == + Approx(0).margin(0.5)); // this is not physics validation + + CHECK((pSum - plab).getNorm() / 1_GeV == + Approx(0).margin(plab.getNorm() * 0.05 / 1_GeV)); + CHECK(pSum.getNorm() / P0 == Approx(1).margin(0.05)); + // [[maybe_unused]] const GrammageType length = + // model.getInteractionLength(particle); + // CHECK(length / 1_g * 1_cm * 1_cm == + // Approx(30).margin(20)); // this is no physics validation + }*/ + + // SECTION("InteractionInterface") + { + HEPEnergyType const P0 = 10_TeV; + Code const pid = Code::Proton; auto [stack, viewPtr] = setup::testing::setup_stack( - get_nucleus_code(8, 4), P0, (setup::Environment::BaseNodeType* const)nodePtr, cs); + pid, P0, (setup::Environment::BaseNodeType* const)nodePtr, cs); MomentumVector plab = - MomentumVector(cs, {P0, 0_eV, 0_eV}); // this is secret knowledge about setupStack + MomentumVector(cs, {P0, 0_eV, 0_eV}); // this is secret knowledge about setup::StackView& view = *viewPtr; - auto particle = stack->first(); - - Interaction model; - -#ifndef __clang__ - // This is very obscure since it fails for -O2, but for both clang and gcc ??? - model.doInteraction(view); + // @todo This is very obscure since it fails for -O2, but for both clang and gcc ??? + model.doInteraction(view, pid, Code::Oxygen, + {sqrt(static_pow<2>(P0) + static_pow<2>(get_mass(pid))), plab}, + {Oxygen::mass, {cs, 0_GeV, 0_GeV, 0_GeV}}); auto const pSum = sumMomentum(view, cs); @@ -262,9 +305,9 @@ TEST_CASE("EposInterface", "modules") { CHECK((pSum - plab).getNorm() / 1_GeV == Approx(0).margin(plab.getNorm() * 0.05 / 1_GeV)); CHECK(pSum.getNorm() / P0 == Approx(1).margin(0.05)); -#endif - [[maybe_unused]] const GrammageType length = model.getInteractionLength(particle); - CHECK(length / 1_g * 1_cm * 1_cm == - Approx(30).margin(20)); // this is no physics validation + // [[maybe_unused]] const GrammageType length = + // model.getInteractionLength(particle); + // CHECK(length / 1_g * 1_cm * 1_cm == + // Approx(30).margin(20)); // this is no physics validation } -} +} \ No newline at end of file diff --git a/tests/modules/testPythia8.cpp b/tests/modules/testPythia8.cpp index 0e449f16b6a38eaeac30b95a3475c7b302532f76..d54324be5dc00409defa1fc183c2f545f5263350 100644 --- a/tests/modules/testPythia8.cpp +++ b/tests/modules/testPythia8.cpp @@ -94,6 +94,8 @@ auto sumMomentum(TStackView const& view, CoordinateSystemPtr const& vCS) { TEST_CASE("Pythia8Interface", "modules") { logging::set_level(logging::level::info); + + auto const rootCS = get_root_CoordinateSystem(); auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Proton); auto const& cs = *csPtr; { @@ -166,7 +168,6 @@ TEST_CASE("Pythia8Interface", "modules") { auto [stackPtr, secViewPtr] = setup::testing::setup_stack( Code::Proton, 7_TeV, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); auto& view = *secViewPtr; - auto const particle = stackPtr->getNextParticle(); corsika::pythia8::Interaction collision; @@ -179,34 +180,20 @@ TEST_CASE("Pythia8Interface", "modules") { CHECK_FALSE(collision.canInteract(Code::Electron)); // nuclei not supported - CHECK_THROWS(collision.getCrossSection(Code::Proton, Code::Helium, 1_TeV)); std::tuple<CrossSectionType, CrossSectionType> xs_test = - collision.getCrossSection(Code::Iron, Code::Hydrogen, 1_GeV); - CHECK(std::get<0>(xs_test) == std::numeric_limits<double>::infinity() * 1_mb); - CHECK(std::get<1>(xs_test) == std::numeric_limits<double>::infinity() * 1_mb); - - collision.getInteractionLength(particle); - - collision.doInteraction(view); - [[maybe_unused]] const GrammageType length = collision.getInteractionLength(particle); - CHECK(length / 1_kg * square(1_m) == Approx(43.04).margin(5e-1)); - CHECK(view.getSize() == 38); - } - - SECTION("pythia nucleus projectile") { - - // this is a projectile nucleus with very little energy - auto [stackPtr, secViewPtr] = setup::testing::setup_stack( - Code::Oxygen, 17_GeV, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); - auto& view = *secViewPtr; - auto particle = stackPtr->first(); - - corsika::pythia8::Interaction collision; - - GrammageType lambda_test = collision.getInteractionLength(particle); - CHECK(lambda_test == std::numeric_limits<double>::infinity() * 1_g / (1_cm * 1_cm)); - - CHECK_THROWS(collision.doInteraction(view)); + collision.getCrossSectionInelEla( + Code::Proton, Code::Hydrogen, + {sqrt(static_pow<2>(Proton::mass) + static_pow<2>(100_GeV)), + {rootCS, {0_eV, 0_eV, 100_GeV}}}, + {Hydrogen::mass, {rootCS, {0_eV, 0_eV, 0_eV}}}); + CHECK(std::get<0>(xs_test) / 1_mb == Approx(314).margin(2)); + CHECK(std::get<1>(xs_test) / 1_mb == Approx(69).margin(2)); + + collision.doInteraction(view, Code::Proton, Code::Hydrogen, + {sqrt(static_pow<2>(Proton::mass) + static_pow<2>(100_GeV)), + {rootCS, {0_eV, 0_eV, 100_GeV}}}, + {Hydrogen::mass, {rootCS, {0_eV, 0_eV, 0_eV}}}); + CHECK(view.getSize() == 12); } SECTION("pythia too low energy") { @@ -215,14 +202,14 @@ TEST_CASE("Pythia8Interface", "modules") { auto [stackPtr, secViewPtr] = setup::testing::setup_stack( Code::Neutron, 1_GeV, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); auto& view = *secViewPtr; - auto particle = stackPtr->first(); corsika::pythia8::Interaction collision; - GrammageType lambda_test = collision.getInteractionLength(particle); - CHECK(lambda_test == std::numeric_limits<double>::infinity() * 1_g / (1_cm * 1_cm)); - - CHECK_THROWS(collision.doInteraction(view)); + CHECK_THROWS(collision.doInteraction( + view, Code::Neutron, Code::Hydrogen, + {sqrt(static_pow<2>(Neutron::mass) + static_pow<2>(1_GeV)), + {rootCS, {0_eV, 0_eV, 1_GeV}}}, + {Hydrogen::mass, {rootCS, {0_eV, 0_eV, 0_eV}}})); } SECTION("pythia wrong target") { @@ -244,6 +231,34 @@ TEST_CASE("Pythia8Interface", "modules") { corsika::pythia8::Interaction collision; - CHECK_THROWS(collision.doInteraction(view)); + CHECK(collision.getCrossSection( + Code::Proton, Code::Iron, + {sqrt(static_pow<2>(Proton::mass) + static_pow<2>(100_GeV)), + {rootCS, {0_eV, 0_eV, 100_GeV}}}, + {Iron::mass, {rootCS, {0_eV, 0_eV, 0_eV}}}) / + 1_mb == + Approx(0)); + + CHECK_THROWS(collision.doInteraction( + view, Code::Proton, Code::Iron, + {sqrt(static_pow<2>(Proton::mass) + static_pow<2>(100_GeV)), + {rootCS, {0_eV, 0_eV, 100_GeV}}}, + {Iron::mass, {rootCS, {0_eV, 0_eV, 0_eV}}})); + } + + SECTION("pythia wrong projectile") { + + // resonable projectile, but tool low energy + auto [stackPtr, secViewPtr] = setup::testing::setup_stack( + Code::Iron, 1_GeV, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); + { [[maybe_unused]] auto const& dummy_StackPtr = stackPtr; } + + corsika::pythia8::Interaction collision; + + CHECK_THROWS( + collision.doInteraction(*secViewPtr, Code::Iron, Code::Hydrogen, + {sqrt(static_pow<2>(Iron::mass) + static_pow<2>(100_GeV)), + {rootCS, {0_eV, 0_eV, 100_GeV}}}, + {Hydrogen::mass, {rootCS, {0_eV, 0_eV, 0_eV}}})); } } diff --git a/tests/modules/testQGSJetII.cpp b/tests/modules/testQGSJetII.cpp index 84f1b4ebe8fc7f6edcd9c2aac0c2ec1b0336f8da..966928f9a06d8dfade6ddebca63c9639a7d2a90c 100644 --- a/tests/modules/testQGSJetII.cpp +++ b/tests/modules/testQGSJetII.cpp @@ -6,8 +6,7 @@ * the license. */ -#include <corsika/modules/qgsjetII/Interaction.hpp> -#include <corsika/modules/qgsjetII/ParticleConversion.hpp> +#include <corsika/modules/QGSJetII.hpp> #include <corsika/framework/core/ParticleProperties.hpp> #include <corsika/framework/core/PhysicalUnits.hpp> @@ -49,6 +48,7 @@ auto sumMomentum(TStackView const& view, CoordinateSystemPtr const& vCS) { TEST_CASE("QgsjetII", "[processes]") { logging::set_level(logging::level::info); + RNGManager<>::getInstance().registerRandomStream("qgsjet"); SECTION("Corsika -> QgsjetII") { CHECK(corsika::qgsjetII::convertToQgsjetII(PiMinus::code) == @@ -91,6 +91,15 @@ TEST_CASE("QgsjetII", "[processes]") { CHECK(corsika::qgsjetII::getQgsjetIIXSCode(Code::PiMinus) == corsika::qgsjetII::QgsjetIIXSClass::LightMesons); } + + SECTION("valid") { + + corsika::qgsjetII::InteractionModel model; + + CHECK_FALSE(model.isValid(Code::Electron, Code::Proton, 1_TeV)); + CHECK_FALSE(model.isValid(Code::Proton, Code::Electron, 1_TeV)); + CHECK_FALSE(model.isValid(Code::Proton, Code::Proton, 1_GeV)); + } } #include <corsika/framework/geometry/Point.hpp> @@ -115,25 +124,23 @@ TEST_CASE("QgsjetIIInterface", "interaction,processes") { logging::set_level(logging::level::info); auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Oxygen); + auto const& cs = *csPtr; [[maybe_unused]] auto const& env_dummy = env; [[maybe_unused]] auto const& node_dummy = nodePtr; - RNGManager<>::getInstance().registerRandomStream("qgsjet"); - SECTION("InteractionInterface") { auto [stackPtr, secViewPtr] = setup::testing::setup_stack( Code::Proton, 110_GeV, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); setup::StackView& view = *(secViewPtr.get()); - auto particle = stackPtr->first(); auto projectile = secViewPtr->getProjectile(); auto const projectileMomentum = projectile.getMomentum(); - corsika::qgsjetII::Interaction model; - model.doInteraction(view); - [[maybe_unused]] const GrammageType length = model.getInteractionLength(particle); - - CHECK(length / (1_g / square(1_cm)) == Approx(93.04).margin(0.1)); + corsika::qgsjetII::InteractionModel model; + model.doInteraction(view, Code::Proton, Code::Oxygen, + {sqrt(static_pow<2>(110_GeV) + static_pow<2>(Proton::mass)), + MomentumVector{cs, 110_GeV, 0_GeV, 0_GeV}}, + {Oxygen::mass, MomentumVector{cs, {0_eV, 0_eV, 0_eV}}}); /* ********************************** As it turned out already two times (#291 and #307) that the detailed output of @@ -152,24 +159,25 @@ TEST_CASE("QgsjetIIInterface", "interaction,processes") { SECTION("InteractionInterface Nuclei") { + HEPEnergyType const P0 = 20100_GeV; + MomentumVector const plab = MomentumVector(cs, {P0, 0_eV, 0_eV}); + Code const pid = get_nucleus_code(60, 30); auto [stackPtr, secViewPtr] = setup::testing::setup_stack( - get_nucleus_code(60, 30), 20100_GeV, - (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); + pid, P0, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); setup::StackView& view = *(secViewPtr.get()); - auto particle = stackPtr->first(); - auto projectile = secViewPtr->getProjectile(); - auto const projectileMomentum = projectile.getMomentum(); - corsika::qgsjetII::Interaction model; - model.doInteraction(view); // this also should produce some fragments + HEPEnergyType const Elab = sqrt(static_pow<2>(P0) + static_pow<2>(get_mass(pid))); + FourMomentum const projectileP4(Elab, plab); + FourMomentum const targetP4(Oxygen::mass, MomentumVector(cs, {0_eV, 0_eV, 0_eV})); + view.clear(); + + corsika::qgsjetII::InteractionModel model; + model.doInteraction(view, pid, Code::Oxygen, projectileP4, + targetP4); // this also should produce some fragments CHECK(view.getSize() == Approx(300).margin(150)); // this is not physics validation int countFragments = 0; for (auto const& sec : view) { countFragments += (is_nucleus(sec.getPID())); } CHECK(countFragments == Approx(4).margin(2)); // this is not physics validation - [[maybe_unused]] const GrammageType length = model.getInteractionLength(particle); - - CHECK(length / (1_g / square(1_cm)) == - Approx(12).margin(2)); // this is not physics validation } SECTION("Heavy nuclei") { @@ -178,38 +186,36 @@ TEST_CASE("QgsjetIIInterface", "interaction,processes") { get_nucleus_code(1000, 1000), 1100_GeV, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); setup::StackView& view = *(secViewPtr.get()); - auto particle = stackPtr->first(); auto projectile = secViewPtr->getProjectile(); auto const projectileMomentum = projectile.getMomentum(); - corsika::qgsjetII::Interaction model; + corsika::qgsjetII::InteractionModel model; + FourMomentum const aP4(100_GeV, {cs, 99_GeV, 0_GeV, 0_GeV}); + FourMomentum const bP4(1_TeV, {cs, 0.9_TeV, 0_GeV, 0_GeV}); + + CHECK(model.getCrossSection(get_nucleus_code(10, 5), get_nucleus_code(1000, 500), aP4, + bP4) / + 1_mb == + Approx(0)); + CHECK(model.getCrossSection(Code::Nucleus, Code::Nucleus, aP4, bP4) / 1_mb == + Approx(0)); CHECK_THROWS( - model.getCrossSection(Code::Nucleus, Code::Nucleus, 100_GeV, 10., 1000.)); - CHECK_THROWS( - model.getCrossSection(Code::Nucleus, Code::Nucleus, 100_GeV, 1000., 10.)); - CHECK_THROWS(model.doInteraction(view)); - CHECK_THROWS(model.getInteractionLength(particle)); + model.doInteraction(view, get_nucleus_code(1000, 500), Code::Oxygen, aP4, bP4)); } SECTION("Allowed Particles") { - { // electron - auto [stackPtr, secViewPtr] = setup::testing::setup_stack( - Code::Electron, 100_GeV, (setup::Environment::BaseNodeType* const)nodePtr, - *csPtr); - [[maybe_unused]] setup::StackView& view = *(secViewPtr.get()); - auto particle = stackPtr->first(); - corsika::qgsjetII::Interaction model; - GrammageType const length = model.getInteractionLength(particle); - CHECK(length / (1_g / square(1_cm)) == std::numeric_limits<double>::infinity()); - } + { // pi0 is internally converted into pi+/pi- auto [stackPtr, secViewPtr] = setup::testing::setup_stack( Code::Pi0, 1000_GeV, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); [[maybe_unused]] setup::StackView& view = *(secViewPtr.get()); [[maybe_unused]] auto particle = stackPtr->first(); - corsika::qgsjetII::Interaction model; - model.doInteraction(view); + corsika::qgsjetII::InteractionModel model; + model.doInteraction(view, Code::Pi0, Code::Oxygen, + {sqrt(static_pow<2>(1_TeV) + static_pow<2>(Pi0::mass)), + MomentumVector{cs, 1_TeV, 0_GeV, 0_GeV}}, + {Oxygen::mass, MomentumVector{cs, 0_eV, 0_eV, 0_eV}}); CHECK(view.getSize() == Approx(10).margin(8)); // this is not physics validation } { // rho0 is internally converted into pi-/pi+ @@ -217,8 +223,11 @@ TEST_CASE("QgsjetIIInterface", "interaction,processes") { Code::Rho0, 1000_GeV, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); [[maybe_unused]] setup::StackView& view = *(secViewPtr.get()); [[maybe_unused]] auto particle = stackPtr->first(); - corsika::qgsjetII::Interaction model; - model.doInteraction(view); + corsika::qgsjetII::InteractionModel model; + model.doInteraction(view, Code::Rho0, Code::Oxygen, + {sqrt(static_pow<2>(1_TeV) + static_pow<2>(Rho0::mass)), + MomentumVector{cs, 1_TeV, 0_GeV, 0_GeV}}, + {Oxygen::mass, MomentumVector{cs, 0_eV, 0_eV, 0_eV}}); CHECK(view.getSize() == Approx(25).margin(20)); // this is not physics validation } { // Lambda is internally converted into neutron @@ -227,8 +236,11 @@ TEST_CASE("QgsjetIIInterface", "interaction,processes") { *csPtr); [[maybe_unused]] setup::StackView& view = *(secViewPtr.get()); [[maybe_unused]] auto particle = stackPtr->first(); - corsika::qgsjetII::Interaction model; - model.doInteraction(view); + corsika::qgsjetII::InteractionModel model; + model.doInteraction(view, Code::Lambda0, Code::Oxygen, + {sqrt(static_pow<2>(100_GeV) + static_pow<2>(Lambda0::mass)), + MomentumVector{cs, 100_GeV, 0_GeV, 0_GeV}}, + {Oxygen::mass, MomentumVector{cs, 0_eV, 0_eV, 0_eV}}); CHECK(view.getSize() == Approx(25).margin(20)); // this is not physics validation } { // AntiLambda is internally converted into anti neutron @@ -237,8 +249,11 @@ TEST_CASE("QgsjetIIInterface", "interaction,processes") { *csPtr); [[maybe_unused]] setup::StackView& view = *(secViewPtr.get()); [[maybe_unused]] auto particle = stackPtr->first(); - corsika::qgsjetII::Interaction model; - model.doInteraction(view); + corsika::qgsjetII::InteractionModel model; + model.doInteraction(view, Code::Lambda0Bar, Code::Oxygen, + {sqrt(static_pow<2>(1_TeV) + static_pow<2>(Lambda0Bar::mass)), + MomentumVector{cs, 1_TeV, 0_GeV, 0_GeV}}, + {Oxygen::mass, MomentumVector{cs, 0_eV, 0_eV, 0_eV}}); CHECK(view.getSize() == Approx(70).margin(67)); // this is not physics validation } } diff --git a/tests/modules/testSibyll.cpp b/tests/modules/testSibyll.cpp index 14051538285f203739b84ae4a3b7eaebad29ada1..042dc72a873d657b3e7da682fdead5d15df67e86 100644 --- a/tests/modules/testSibyll.cpp +++ b/tests/modules/testSibyll.cpp @@ -13,6 +13,7 @@ #include <corsika/framework/core/PhysicalUnits.hpp> #include <corsika/framework/geometry/Point.hpp> #include <corsika/framework/random/RNGManager.hpp> +#include <corsika/framework/utility/COMBoost.hpp> #include <catch2/catch.hpp> #include <tuple> @@ -55,23 +56,23 @@ TEST_CASE("Sibyll", "modules") { CHECK_FALSE(corsika::sibyll::canInteract(Code::Electron)); CHECK_FALSE(corsika::sibyll::canInteract(Code::SigmaC0)); - CHECK_FALSE(corsika::sibyll::canInteract(Code::Nucleus)); + CHECK_FALSE(corsika::sibyll::canInteract(Code::Iron)); CHECK_FALSE(corsika::sibyll::canInteract(Code::Helium)); } SECTION("cross-section type") { - CHECK(corsika::sibyll::getSibyllXSCode(Code::Helium) == 0); CHECK(corsika::sibyll::getSibyllXSCode(Code::Proton) == 1); CHECK(corsika::sibyll::getSibyllXSCode(Code::Electron) == 0); CHECK(corsika::sibyll::getSibyllXSCode(Code::K0Long) == 3); CHECK(corsika::sibyll::getSibyllXSCode(Code::SigmaPlus) == 1); CHECK(corsika::sibyll::getSibyllXSCode(Code::PiMinus) == 2); + CHECK(corsika::sibyll::getSibyllXSCode(Code::Helium) == 0); } SECTION("sibyll mass") { CHECK_FALSE(corsika::sibyll::getSibyllMass(Code::Electron) == 0_GeV); // Nucleus not a particle - CHECK_THROWS(corsika::sibyll::getSibyllMass(Code::Nucleus)); + CHECK_THROWS(corsika::sibyll::getSibyllMass(Code::Iron)); // Higgs not a particle in Sibyll CHECK_THROWS(corsika::sibyll::getSibyllMass(Code::H0)); } @@ -82,7 +83,6 @@ TEST_CASE("Sibyll", "modules") { #include <corsika/framework/geometry/Vector.hpp> #include <corsika/framework/core/PhysicalUnits.hpp> - #include <corsika/framework/core/ParticleProperties.hpp> #include <SetupTestEnvironment.hpp> @@ -100,68 +100,76 @@ auto sumMomentum(TStackView const& view, CoordinateSystemPtr const& vCS) { return sum; } -TEST_CASE("SibyllInteractionInterface", "modules") { +TEST_CASE("SibyllInterface", "modules") { - logging::set_level(logging::level::info); + logging::set_level(logging::level::trace); + // the environment and stack should eventually disappear from here auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Oxygen); auto const& cs = *csPtr; { [[maybe_unused]] auto const& env_dummy = env; } + auto [stack, viewPtr] = setup::testing::setup_stack( + Code::Proton, 10_GeV, (setup::Environment::BaseNodeType* const)nodePtr, cs); + setup::StackView& view = *viewPtr; + RNGManager<>::getInstance().registerRandomStream("sibyll"); SECTION("InteractionInterface - valid targets") { - Interaction model; + corsika::sibyll::InteractionModel model; // sibyll only accepts protons or nuclei with 4<=A<=18 as targets - CHECK_FALSE(model.isValidTarget(Code::Electron)); - CHECK(model.isValidTarget(Code::Hydrogen)); - CHECK_FALSE(model.isValidTarget(Code::Deuterium)); - CHECK(model.isValidTarget(Code::Helium)); - CHECK_FALSE(model.isValidTarget(Code::Helium3)); - CHECK_FALSE(model.isValidTarget(Code::Iron)); - CHECK(model.isValidTarget(Code::Oxygen)); + CHECK_FALSE(model.isValid(Code::Proton, Code::Electron, 100_GeV)); + CHECK(model.isValid(Code::Proton, Code::Hydrogen, 100_GeV)); + CHECK_FALSE(model.isValid(Code::Proton, Code::Deuterium, 100_GeV)); + CHECK(model.isValid(Code::Proton, Code::Helium, 100_GeV)); + CHECK_FALSE(model.isValid(Code::Proton, Code::Helium3, 100_GeV)); + CHECK_FALSE(model.isValid(Code::Proton, Code::Iron, 100_GeV)); + CHECK(model.isValid(Code::Proton, Code::Oxygen, 100_GeV)); + // beam particles + CHECK_FALSE(model.isValid(Code::Electron, Code::Oxygen, 100_GeV)); + CHECK_FALSE(model.isValid(Code::Iron, Code::Oxygen, 100_GeV)); + // energy too low + CHECK_FALSE(model.isValid(Code::Proton, Code::Proton, 9_GeV)); + CHECK(model.isValid(Code::Proton, Code::Proton, 11_GeV)); + // energy too high + CHECK_FALSE(model.isValid(Code::Proton, Code::Proton, 1000001_GeV)); + CHECK(model.isValid(Code::Proton, Code::Proton, 999999_GeV)); // hydrogen target == proton target == neutron target + FourMomentum const aP4(100_GeV, {cs, 99_GeV, 0_GeV, 0_GeV}); + FourMomentum const bP4(1_GeV, {cs, 0_GeV, 0_GeV, 0_GeV}); auto const [xs_prod_pp, xs_ela_pp] = - model.getCrossSection(Code::Proton, Code::Proton, 100_GeV); + model.getCrossSectionInelEla(Code::Proton, Code::Proton, aP4, bP4); auto const [xs_prod_pn, xs_ela_pn] = - model.getCrossSection(Code::Proton, Code::Neutron, 100_GeV); + model.getCrossSectionInelEla(Code::Proton, Code::Neutron, aP4, bP4); auto const [xs_prod_pHydrogen, xs_ela_pHydrogen] = - model.getCrossSection(Code::Proton, Code::Hydrogen, 100_GeV); + model.getCrossSectionInelEla(Code::Proton, Code::Hydrogen, aP4, bP4); CHECK(xs_prod_pp == xs_prod_pHydrogen); CHECK(xs_prod_pp == xs_prod_pn); CHECK(xs_ela_pp == xs_ela_pHydrogen); CHECK(xs_ela_pn == xs_ela_pHydrogen); - CHECK_THROWS(convertFromSibyll(corsika::sibyll::SibyllCode::Unknown)); + // invalids + auto const xs_prod_0 = model.getCrossSection(Code::Electron, Code::Proton, aP4, bP4); + CHECK(xs_prod_0 / 1_mb == Approx(0)); + CHECK_THROWS(model.doInteraction(view, Code::Electron, Code::Proton, aP4, bP4)); - // out of range - // beam particle - CHECK_THROWS( - std::get<0>(model.getCrossSection(Code::Electron, Code::Hydrogen, 100_GeV))); - // target particle - CHECK(std::get<0>(model.getCrossSection(Code::Proton, Code::Electron, 100_GeV)) == - std::numeric_limits<double>::infinity() * 1_mb); - // energy out of range - CHECK_THROWS(std::get<0>(model.getCrossSection(Code::Proton, Code::Hydrogen, 5_GeV))); + CHECK_THROWS(convertFromSibyll(corsika::sibyll::SibyllCode::Unknown)); } SECTION("InteractionInterface - low energy") { const HEPEnergyType P0 = 60_GeV; - auto [stack, viewPtr] = setup::testing::setup_stack( - Code::Proton, P0, (setup::Environment::BaseNodeType* const)nodePtr, cs); - MomentumVector plab = - MomentumVector(cs, {P0, 0_eV, 0_eV}); // this is secret knowledge about setupStack - setup::StackView& view = *viewPtr; - - auto particle = stack->first(); - + MomentumVector const plab = MomentumVector(cs, {P0, 0_eV, 0_eV}); // also print particles after sibyll was called - Interaction model(true); - - model.doInteraction(view); + corsika::sibyll::InteractionModel model; + model.setVerbose(true); + HEPEnergyType const Elab = sqrt(static_pow<2>(P0) + static_pow<2>(Proton::mass)); + FourMomentum const projectileP4(Elab, plab); + FourMomentum const nucleusP4(Oxygen::mass, MomentumVector(cs, {0_eV, 0_eV, 0_eV})); + view.clear(); + model.doInteraction(view, Code::Proton, Code::Oxygen, projectileP4, nucleusP4); auto const pSum = sumMomentum(view, cs); /* @@ -227,82 +235,44 @@ TEST_CASE("SibyllInteractionInterface", "modules") { CHECK((pSum - plab).getNorm() / 1_GeV == Approx(0).margin(plab.getNorm() * 0.05 / 1_GeV)); CHECK(pSum.getNorm() / P0 == Approx(1).margin(0.05)); - [[maybe_unused]] GrammageType const length = model.getInteractionLength(particle); - CHECK(length / 1_g * 1_cm * 1_cm == Approx(88.7).margin(0.1)); + [[maybe_unused]] CrossSectionType const cx = + model.getCrossSection(Code::Proton, Code::Oxygen, projectileP4, nucleusP4); + CHECK(cx / 1_mb == Approx(300).margin(1)); // CHECK(view.getEntries() == 9); //! \todo: this was 20 before refactory-2020: check // "also sibyll not stable wrt. to compiler // changes" } - SECTION("InteractionInterface - energy too low") { - - const HEPEnergyType P0 = 5_GeV; - auto [stack, viewPtr] = setup::testing::setup_stack( - Code::Proton, P0, (setup::Environment::BaseNodeType* const)nodePtr, cs); - MomentumVector plab = - MomentumVector(cs, {P0, 0_eV, 0_eV}); // this is secret knowledge about setupStack - setup::StackView& view = *viewPtr; - - auto particle = stack->first(); - - Interaction model; - CHECK_THROWS(model.doInteraction(view)); - - [[maybe_unused]] GrammageType const length = model.getInteractionLength(particle); - CHECK(model.getInteractionLength(particle) / 1_g * 1_cm * 1_cm == - std::numeric_limits<double>::infinity()); - } - - SECTION("InteractionInterface - energy too high") { - - const HEPEnergyType P0 = 1000_EeV; - auto [stack, viewPtr] = setup::testing::setup_stack( - Code::Proton, P0, (setup::Environment::BaseNodeType* const)nodePtr, cs); - { [[maybe_unused]] auto const& dummy1 = stack; } - MomentumVector plab = - MomentumVector(cs, {P0, 0_eV, 0_eV}); // this is secret knowledge about setupStack - setup::StackView& view = *viewPtr; - - Interaction model; - CHECK_THROWS(model.doInteraction(view)); - } - - SECTION("InteractionInterface - target nucleus out of range") { - auto [env1, csPtr1, nodePtr1] = setup::testing::setup_environment(Code::Argon); - { [[maybe_unused]] auto const& dummy1 = env1; } - auto const& cs1 = *csPtr1; - const HEPEnergyType P0 = 150_GeV; - auto [stack, viewPtr] = setup::testing::setup_stack( - Code::Electron, P0, (setup::Environment::BaseNodeType* const)nodePtr1, cs1); - { [[maybe_unused]] auto const& dummy1 = stack; } - MomentumVector plab = MomentumVector( - cs1, {P0, 0_eV, 0_eV}); // this is secret knowledge about setupStack - setup::StackView& view = *viewPtr; - - Interaction model; - CHECK_THROWS(model.doInteraction(view)); - } - SECTION("NuclearInteractionInterface") { - auto [stack, viewPtr] = - setup::testing::setup_stack(get_nucleus_code(8, 4), 900_GeV, - (setup::Environment::BaseNodeType* const)nodePtr, cs); - setup::StackView& view = *viewPtr; - auto particle = stack->first(); - - Interaction hmodel; - NuclearInteraction model(hmodel, *env); - - model.doInteraction(view); - [[maybe_unused]] const GrammageType length = model.getInteractionLength(particle); - // Felix, are those changes OK? Below are the checks before refactory-2020 - // CHECK(length / 1_g * 1_cm * 1_cm == Approx(44.2).margin(.1)); - // CHECK(view.getSize() == 11); - CHECK(length / 1_g * 1_cm * 1_cm == - Approx(31).margin(5)); // this is not physics validation - // CHECK(view.getSize() == 20); // also sibyll not stable wrt. to compiler changes - CHECK(view.getSize() == Approx(100).margin(90)); // this is not physics validation + HEPMomentumType const P0 = 50_TeV; + MomentumVector const plab = MomentumVector(cs, {P0, 0_eV, 0_eV}); + corsika::sibyll::InteractionModel hmodel; + NuclearInteractionModel model(hmodel, *env); + + CHECK(model.isValid(Code::Helium, Code::Oxygen, 100_GeV)); + CHECK_FALSE(model.isValid(Code::PiPlus, Code::Oxygen, 100_GeV)); + CHECK_FALSE(model.isValid(Code::Electron, Code::Oxygen, 100_GeV)); + + Code const pid = Code::Oxygen; + HEPEnergyType const Elab = sqrt(static_pow<2>(P0) + static_pow<2>(get_mass(pid))); + FourMomentum const P4(Elab, plab); + FourMomentum const targetP4(get_mass(Code::Oxygen), + MomentumVector(cs, {0_eV, 0_eV, 0_eV})); + model.doInteraction(view, pid, Code::Oxygen, P4, targetP4); + CrossSectionType const cx = model.getCrossSection(pid, Code::Oxygen, P4, targetP4); + CHECK(cx / 1_mb == Approx(1250).margin(100)); // this is not physics validation + CHECK(view.getSize() == Approx(150).margin(140)); // this is not physics validation + + // invalid to underlying model + FourMomentum P4mu( + 100_GeV, + {cs, {sqrt(static_pow<2>(100_GeV) - static_pow<2>(MuPlus::mass)), 0_eV, 0_eV}}); + CrossSectionType const cx0 = + model.getCrossSection(Code::MuPlus, Code::Oxygen, P4mu, targetP4); + CHECK(cx0 / 1_mb == Approx(0)); + + CHECK_THROWS(model.doInteraction(view, Code::MuPlus, Code::Oxygen, P4mu, targetP4)); } } @@ -341,7 +311,7 @@ TEST_CASE("SibyllDecayInterface", "modules") { Decay model; model.printDecayConfig(); - [[maybe_unused]] const TimeType time = model.getLifetime(particle); + [[maybe_unused]] TimeType const time = model.getLifetime(particle); auto const gamma = particle.getEnergy() / particle.getMass(); CHECK(time == get_lifetime(Code::Lambda0) * gamma); model.doDecay(view); @@ -372,7 +342,7 @@ TEST_CASE("SibyllDecayInterface", "modules") { CHECK(model.isDecayHandled(Code::PiMinus)); CHECK_FALSE(model.isDecayHandled(Code::KPlus)); - const std::vector<Code> particleTestList = {Code::PiPlus, Code::PiMinus, Code::KPlus, + std::vector<Code> const particleTestList = {Code::PiPlus, Code::PiMinus, Code::KPlus, Code::Lambda0Bar, Code::D0Bar}; // setup decays diff --git a/tests/modules/testTracking.cpp b/tests/modules/testTracking.cpp index d1d967d6f38e58a9b5cff769589939bf0042ed48..1710844b19c6423842bc7b2a9a95bed9b084ea6b 100644 --- a/tests/modules/testTracking.cpp +++ b/tests/modules/testTracking.cpp @@ -75,7 +75,7 @@ TEMPLATE_TEST_CASE("Tracking", "tracking", tracking_leapfrog_curved::Tracking, // for algorithms that know magnetic deflections choose: +-50uT, 0uT // otherwise just 0uT auto Bfield = GENERATE_COPY(filter( - [isParallel]([[maybe_unused]] MagneticFluxType v) { + []([[maybe_unused]] MagneticFluxType v) { if constexpr (std::is_same_v<TestType, tracking_line::Tracking>) return v == 0_uT; else @@ -141,19 +141,19 @@ TEMPLATE_TEST_CASE("Tracking", "tracking", tracking_leapfrog_curved::Tracking, MagneticFieldVector magneticfield(cs, 0_T, 0_T, Bfield); target->setModelProperties<MyHomogeneousModel>( Medium::AirDry1Atm, magneticfield, 1_g / (1_m * 1_m * 1_m), - NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<float>{1.})); + NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<double>{1.})); target_neutral->setModelProperties<MyHomogeneousModel>( Medium::AirDry1Atm, magneticfield, 1_g / (1_m * 1_m * 1_m), - NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<float>{1.})); + NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<double>{1.})); target_2->setModelProperties<MyHomogeneousModel>( Medium::AirDry1Atm, magneticfield, 1_g / (1_m * 1_m * 1_m), - NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<float>{1.})); + NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<double>{1.})); target_2_behind->setModelProperties<MyHomogeneousModel>( Medium::AirDry1Atm, magneticfield, 1_g / (1_m * 1_m * 1_m), - NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<float>{1.})); + NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<double>{1.})); target_2_partly_behind->setModelProperties<MyHomogeneousModel>( Medium::AirDry1Atm, magneticfield, 1_g / (1_m * 1_m * 1_m), - NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<float>{1.})); + NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<double>{1.})); auto* targetPtr = target.get(); auto* targetPtr_2 = target_2.get(); auto* targetPtr_neutral = target_neutral.get(); @@ -287,7 +287,7 @@ TEST_CASE("TrackingLeapFrogCurved") { MagneticFieldVector magneticfield(cs, 100_T, 0_T, 0_uT); target->setModelProperties<MyHomogeneousModel>( Medium::AirDry1Atm, magneticfield, 1_g / (1_m * 1_m * 1_m), - NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<float>{1.})); + NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<double>{1.})); auto* targetPtr = target.get(); worldPtr->addChild(std::move(target)); diff --git a/tests/modules/testUrQMD.cpp b/tests/modules/testUrQMD.cpp index 89bde00d2e1772b943f933311ce3f258e438502b..f75d59b75b44a41d25376d013c9d9e0d813efa32 100644 --- a/tests/modules/testUrQMD.cpp +++ b/tests/modules/testUrQMD.cpp @@ -11,8 +11,8 @@ #include <corsika/framework/core/ParticleProperties.hpp> #include <corsika/framework/core/PhysicalConstants.hpp> #include <corsika/framework/core/PhysicalUnits.hpp> -#include <corsika/framework/geometry/Point.hpp> #include <corsika/framework/geometry/RootCoordinateSystem.hpp> +#include <corsika/framework/geometry/Point.hpp> #include <corsika/framework/geometry/Vector.hpp> #include <corsika/framework/random/RNGManager.hpp> #include <corsika/framework/utility/CorsikaFenv.hpp> @@ -71,73 +71,50 @@ TEST_CASE("UrQMD") { RNGManager<>::getInstance().registerRandomStream("urqmd"); UrQMD urqmd; - SECTION("interaction length") { - auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Nitrogen); - auto const& cs = *csPtr; - { [[maybe_unused]] auto const& env_dummy = env; } - - Code validProjectileCodes[] = {Code::PiPlus, Code::PiMinus, Code::Proton, - Code::AntiProton, Code::AntiNeutron, Code::Neutron, - Code::KPlus, Code::KMinus, Code::K0, - Code::K0Bar, Code::K0Long}; - - for (auto code : validProjectileCodes) { - auto [stack, view] = setup::testing::setup_stack(code, 100_GeV, nodePtr, cs); - CHECK(stack->getEntries() == 1); - CHECK(view->getEntries() == 0); - - // simple check whether the cross-section is non-vanishing - // only nuclei with available tabluated data so far - CHECK(urqmd.getInteractionLength(stack->getNextParticle()) > 1_g / square(1_cm)); - } + auto const rootCS = get_root_CoordinateSystem(); + + SECTION("valid") { + // this is how it is currently done + CHECK_FALSE(urqmd.isValid(Code::K0, Code::Proton)); + CHECK_FALSE(urqmd.isValid(Code::DPlus, Code::Proton)); + CHECK_FALSE(urqmd.isValid(Code::Electron, Code::Proton)); + CHECK_FALSE(urqmd.isValid(Code::Proton, Code::Electron)); + CHECK_FALSE(urqmd.isValid(Code::Oxygen, Code::Oxygen)); + CHECK_FALSE(urqmd.isValid(Code::PiPlus, Code::Omega)); + CHECK_FALSE( + urqmd.isValid(Code::PiPlus, Code::Proton)); // Proton is not a valid target.... + + CHECK_NOTHROW(urqmd.isValid(Code::Proton, Code::Oxygen)); + CHECK_NOTHROW(urqmd.isValid(Code::PiPlus, Code::Argon)); } - SECTION("targets options") { - auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Argon); - auto const& cs = *csPtr; - { [[maybe_unused]] auto const& env_dummy = env; } - auto [stack, view] = setup::testing::setup_stack(Code::Proton, 100_GeV, nodePtr, cs); - [[maybe_unused]] setup::StackView& viewRef = *(view.get()); - CHECK(urqmd.getInteractionLength(stack->getNextParticle()) / 1_g * square(1_cm) == - Approx(105).margin(5)); - } + SECTION("cross sections") { - SECTION("invalid targets options") { - auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Omega); - auto const& cs = *csPtr; - { [[maybe_unused]] auto const& env_dummy = env; } - auto [stack, view] = setup::testing::setup_stack(Code::Neutron, 100_GeV, nodePtr, cs); - [[maybe_unused]] setup::StackView& viewRef = *(view.get()); - CHECK_THROWS(urqmd.getInteractionLength(stack->getNextParticle())); - } + FourMomentum const targetP4{Nitrogen::mass, {rootCS, {0_eV, 0_eV, 0_eV}}}; - SECTION("nucleus projectile") { - auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Oxygen); - [[maybe_unused]] auto const& env_dummy = env; // against warnings - [[maybe_unused]] auto const& node_dummy = nodePtr; // against warnings + HEPMomentumType const P0 = 100_GeV; + Code const validProjectileCodes[] = { + Code::PiPlus, Code::PiMinus, Code::Proton, Code::AntiProton, Code::AntiNeutron, + Code::Neutron, Code::KPlus, Code::KMinus, Code::K0Long}; + // Code::K0, Code::K0Bar are not valid projectiles (no mass eigenstates) + CrossSectionType const checkCX[] = {219_mb, 222_mb, 303_mb, 324_mb, 324_mb, + 303_mb, 189_mb, 198_mb, 172_mb}; - unsigned short constexpr A = 14, Z = 7; - auto [stackPtr, secViewPtr] = setup::testing::setup_stack( - get_nucleus_code(A, Z), 40_GeV, (setup::Environment::BaseNodeType* const)nodePtr, - *csPtr); - [[maybe_unused]] setup::StackView& viewRef = *(secViewPtr.get()); - CHECK(stackPtr->getEntries() == 1); - CHECK(secViewPtr->getEntries() == 0); - - // must be assigned to variable, cannot be used as rvalue?! - auto projectile = secViewPtr->getProjectile(); - auto const projectileMomentum = projectile.getMomentum(); - urqmd.doInteraction(*secViewPtr); - - CHECK(sumCharge(*secViewPtr) == Z + get_charge_number(Code::Oxygen)); + int i = 0; + for (auto code : validProjectileCodes) { + FourMomentum const projectileP4{ + sqrt(static_pow<2>(get_mass(code)) + static_pow<2>(P0)), + {rootCS, {0_GeV, 0_GeV, P0}}}; + auto const cx = urqmd.getCrossSection(code, Code::Nitrogen, projectileP4, targetP4); + CORSIKA_LOG_INFO("UrQMD cross seciton for {} is {} mb", code, cx / 1_mb); + CHECK(cx / 1_mb == Approx(checkCX[i++] / 1_mb).margin(1)); + } - auto const secMomSum = - sumMomentum(*secViewPtr, projectileMomentum.getCoordinateSystem()); - CHECK((secMomSum - projectileMomentum).getNorm() / projectileMomentum.getNorm() == - Approx(0).margin(1e-2)); + // invalid + CHECK_THROWS(urqmd.getTabulatedCrossSection(Code::Proton, Code::Proton, 100_GeV)); } - SECTION("\"special\" projectile") { + SECTION("pion+ projectile") { auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Oxygen); [[maybe_unused]] auto const& env_dummy = env; // against warnings [[maybe_unused]] auto const& node_dummy = nodePtr; // against warnings @@ -151,7 +128,11 @@ TEST_CASE("UrQMD") { auto projectile = secViewPtr->getProjectile(); auto const projectileMomentum = projectile.getMomentum(); - urqmd.doInteraction(*secViewPtr); + FourMomentum const projectileP4{ + sqrt(static_pow<2>(PiPlus::mass) + static_pow<2>(40_GeV)), + {rootCS, {40_GeV, 0_GeV, 0_GeV}}}; + FourMomentum const targetP4{Oxygen::mass, {rootCS, {0_GeV, 0_GeV, 0_GeV}}}; + urqmd.doInteraction(*secViewPtr, Code::PiPlus, Code::Oxygen, projectileP4, targetP4); CHECK(sumCharge(*secViewPtr) == get_charge_number(Code::PiPlus) + get_charge_number(Code::Oxygen)); @@ -162,44 +143,6 @@ TEST_CASE("UrQMD") { Approx(0).margin(1e-2)); } - SECTION("\"special\" projectile and target") { - { - auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Proton); - [[maybe_unused]] auto const& env_dummy = env; // against warnings - [[maybe_unused]] auto const& node_dummy = nodePtr; // against warnings - - auto [stackPtr, secViewPtr] = setup::testing::setup_stack( - Code::PiPlus, 40_GeV, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); - [[maybe_unused]] auto particle = stackPtr->first(); - CHECK_THROWS(urqmd.doInteraction(*secViewPtr)); // Code::Proton not a valid target - } - - { - auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Oxygen); - [[maybe_unused]] auto const& env_dummy = env; // against warnings - [[maybe_unused]] auto const& node_dummy = nodePtr; // against warnings - - auto [stackPtr, secViewPtr] = setup::testing::setup_stack( - Code::PiPlus, 40_GeV, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); - CHECK(stackPtr->getEntries() == 1); - CHECK(secViewPtr->getEntries() == 0); - - // must be assigned to variable, cannot be used as rvalue?! - auto projectile = secViewPtr->getProjectile(); - auto const projectileMomentum = projectile.getMomentum(); - - urqmd.doInteraction(*secViewPtr); - - CHECK(sumCharge(*secViewPtr) == - get_charge_number(Code::PiPlus) + get_charge_number(Code::Oxygen)); - - auto const secMomSum = - sumMomentum(*secViewPtr, projectileMomentum.getCoordinateSystem()); - CHECK((secMomSum - projectileMomentum).getNorm() / projectileMomentum.getNorm() == - Approx(0).margin(1e-2)); - } - } - SECTION("K0Long projectile") { auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Oxygen); [[maybe_unused]] auto const& env_dummy = env; // against warnings @@ -214,7 +157,11 @@ TEST_CASE("UrQMD") { auto projectile = secViewPtr->getProjectile(); auto const projectileMomentum = projectile.getMomentum(); - urqmd.doInteraction(*secViewPtr); + FourMomentum const projectileP4{ + sqrt(static_pow<2>(K0Long::mass) + static_pow<2>(40_GeV)), + {rootCS, {40_GeV, 0_GeV, 0_GeV}}}; + FourMomentum const targetP4{Oxygen::mass, {rootCS, {0_GeV, 0_GeV, 0_GeV}}}; + urqmd.doInteraction(*secViewPtr, Code::K0Long, Code::Oxygen, projectileP4, targetP4); CHECK(sumCharge(*secViewPtr) == get_charge_number(Code::K0Long) + get_charge_number(Code::Oxygen));