/* * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu * * See file AUTHORS for a list of contributors. * * 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. */ #ifndef _include_ProcessSequence_h_ #define _include_ProcessSequence_h_ #include <corsika/process/BaseProcess.h> #include <corsika/process/BoundaryCrossingProcess.h> #include <corsika/process/ContinuousProcess.h> #include <corsika/process/DecayProcess.h> #include <corsika/process/InteractionProcess.h> #include <corsika/process/ProcessReturn.h> #include <corsika/process/SecondariesProcess.h> #include <corsika/process/StackProcess.h> #include <corsika/units/PhysicalUnits.h> #include <cmath> #include <limits> #include <type_traits> namespace corsika::process { /** \class ProcessSequence A compile time static list of processes. The compiler will generate a new type based on template logic containing all the elements. \comment Using CRTP pattern, https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern */ // define a marker (trait class) to tag any class that qualifies as "Process" for the // "ProcessSequence" std::false_type is_process_impl(...); template <class T> using is_process = decltype(is_process_impl(std::declval<T*>())); // this is a marker to track which BaseProcess is also a ProcessSequence template <typename T> struct is_process_sequence : std::false_type {}; template <typename T> bool constexpr is_process_sequence_v = is_process_sequence<T>::value; namespace switch_process { template <typename A, typename B> class SwitchProcess; // fwd-decl. } // to detect SwitchProcesses inside the ProcessSequence template <typename T> struct is_switch_process : std::false_type {}; template <typename T> bool constexpr is_switch_process_v = is_switch_process<T>::value; template <typename A, typename B> struct is_process_sequence<switch_process::SwitchProcess<A, B>> : std::true_type {}; /** T1 and T2 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. */ template <typename T1, typename T2> class ProcessSequence : public BaseProcess<ProcessSequence<T1, T2>> { using T1type = typename std::decay<T1>::type; using T2type = typename std::decay<T2>::type; static bool constexpr t1ProcSeq = is_process_sequence_v<T1type>; static bool constexpr t2ProcSeq = is_process_sequence_v<T2type>; static bool constexpr t1SwitchProc = is_switch_process_v<T1type>; static bool constexpr t2SwitchProc = is_switch_process_v<T2type>; public: T1 A; // this is a reference, if possible T2 B; // this is a reference, if possible ProcessSequence(T1 in_A, T2 in_B) : A(in_A) , B(in_B) {} // example for a trait-based call: // void Hello() const { detail::CallHello<T1,T2>::Call(A, B); } template <typename Particle, typename VTNType> EProcessReturn DoBoundaryCrossing(Particle& p, VTNType const& from, VTNType const& to) { EProcessReturn ret = EProcessReturn::eOk; if constexpr (std::is_base_of_v<BoundaryCrossingProcess<T1type>, T1type> || t1ProcSeq) { ret |= A.DoBoundaryCrossing(p, from, to); } if constexpr (std::is_base_of_v<BoundaryCrossingProcess<T2type>, T2type> || t2ProcSeq) { ret |= B.DoBoundaryCrossing(p, from, to); } return ret; } template <typename TParticle, typename TTrack> EProcessReturn DoContinuous(TParticle& vP, TTrack& vT) { EProcessReturn ret = EProcessReturn::eOk; if constexpr (std::is_base_of_v<ContinuousProcess<T1type>, T1type> || t1ProcSeq) { ret |= A.DoContinuous(vP, vT); } if constexpr (std::is_base_of_v<ContinuousProcess<T2type>, T2type> || t2ProcSeq) { ret |= B.DoContinuous(vP, vT); } return ret; } template <typename TSecondaries> EProcessReturn DoSecondaries(TSecondaries& vS) { EProcessReturn ret = EProcessReturn::eOk; if constexpr (std::is_base_of_v<SecondariesProcess<T1type>, T1type> || t1ProcSeq) { ret |= A.DoSecondaries(vS); } if constexpr (std::is_base_of_v<SecondariesProcess<T2type>, T2type> || t2ProcSeq) { ret |= B.DoSecondaries(vS); } return ret; } /** 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() { bool ret = false; if constexpr (std::is_base_of_v<StackProcess<T1type>, T1type> || t1ProcSeq) { ret |= A.CheckStep(); } if constexpr (std::is_base_of_v<StackProcess<T2type>, T2type> || t2ProcSeq) { ret |= B.CheckStep(); } return ret; } /** Execute the StackProcess-es in the ProcessSequence */ template <typename TStack> EProcessReturn DoStack(TStack& vS) { EProcessReturn ret = EProcessReturn::eOk; if constexpr (std::is_base_of_v<StackProcess<T1type>, T1type> || t1ProcSeq) { if (A.CheckStep()) { ret |= A.DoStack(vS); } } if constexpr (std::is_base_of_v<StackProcess<T2type>, T2type> || t2ProcSeq) { if (B.CheckStep()) { ret |= B.DoStack(vS); } } return ret; } template <typename TParticle, typename TTrack> corsika::units::si::LengthType MaxStepLength(TParticle& vP, TTrack& vTrack) { corsika::units::si::LengthType max_length = // if no other process in the sequence implements it std::numeric_limits<double>::infinity() * corsika::units::si::meter; if constexpr (std::is_base_of_v<ContinuousProcess<T1type>, T1type> || t1ProcSeq) { corsika::units::si::LengthType const len = A.MaxStepLength(vP, vTrack); max_length = std::min(max_length, len); } if constexpr (std::is_base_of_v<ContinuousProcess<T2type>, T2type> || t2ProcSeq) { corsika::units::si::LengthType const len = B.MaxStepLength(vP, vTrack); max_length = std::min(max_length, len); } return max_length; } template <typename TParticle> corsika::units::si::GrammageType GetTotalInteractionLength(TParticle& vP) { return 1. / GetInverseInteractionLength(vP); } template <typename TParticle> corsika::units::si::InverseGrammageType GetTotalInverseInteractionLength( TParticle& vP) { return GetInverseInteractionLength(vP); } template <typename TParticle> corsika::units::si::InverseGrammageType GetInverseInteractionLength(TParticle& vP) { using namespace corsika::units::si; InverseGrammageType tot = 0 * meter * meter / gram; if constexpr (std::is_base_of_v<InteractionProcess<T1type>, T1type> || t1ProcSeq || t1SwitchProc) { tot += A.GetInverseInteractionLength(vP); } if constexpr (std::is_base_of_v<InteractionProcess<T2type>, T2type> || t2ProcSeq || t2SwitchProc) { tot += B.GetInverseInteractionLength(vP); } return tot; } template <typename TParticle, typename TSecondaries> EProcessReturn SelectInteraction( TParticle& vP, TSecondaries& vS, [[maybe_unused]] corsika::units::si::InverseGrammageType lambda_select, corsika::units::si::InverseGrammageType& lambda_inv_count) { if constexpr (t1ProcSeq || t1SwitchProc) { // if A is a process sequence --> check inside const EProcessReturn ret = A.SelectInteraction(vP, vS, lambda_select, lambda_inv_count); // if A did succeed, stop routine if (ret != EProcessReturn::eOk) { return ret; } } else if constexpr (std::is_base_of_v<InteractionProcess<T1type>, T1type>) { // if this is not a ContinuousProcess --> evaluate probability lambda_inv_count += A.GetInverseInteractionLength(vP); // check if we should execute THIS process and then EXIT if (lambda_select < lambda_inv_count) { A.DoInteraction(vS); return EProcessReturn::eInteracted; } } // end branch A if constexpr (t2ProcSeq || t2SwitchProc) { // if A is a process sequence --> check inside const EProcessReturn ret = B.SelectInteraction(vP, vS, lambda_select, lambda_inv_count); // if A did succeed, stop routine if (ret != EProcessReturn::eOk) { return ret; } } else if constexpr (std::is_base_of_v<InteractionProcess<T2type>, T2type>) { // if this is not a ContinuousProcess --> evaluate probability lambda_inv_count += B.GetInverseInteractionLength(vP); // check if we should execute THIS process and then EXIT if (lambda_select < lambda_inv_count) { B.DoInteraction(vS); return EProcessReturn::eInteracted; } } // end branch A return EProcessReturn::eOk; } template <typename TParticle> corsika::units::si::TimeType GetTotalLifetime(TParticle& p) { return 1. / GetInverseLifetime(p); } template <typename TParticle> corsika::units::si::InverseTimeType GetTotalInverseLifetime(TParticle& p) { return GetInverseLifetime(p); } template <typename TParticle> corsika::units::si::InverseTimeType GetInverseLifetime(TParticle& p) { using namespace corsika::units::si; corsika::units::si::InverseTimeType tot = 0 / second; if constexpr (std::is_base_of_v<DecayProcess<T1type>, T1type> || t1ProcSeq) { tot += A.GetInverseLifetime(p); } if constexpr (std::is_base_of_v<DecayProcess<T2type>, T2type> || t2ProcSeq) { tot += B.GetInverseLifetime(p); } return tot; } // select decay process template <typename TParticle, typename TSecondaries> EProcessReturn SelectDecay( TParticle& vP, TSecondaries& vS, [[maybe_unused]] corsika::units::si::InverseTimeType decay_select, corsika::units::si::InverseTimeType& decay_inv_count) { if constexpr (t1ProcSeq) { // if A is a process sequence --> check inside const EProcessReturn ret = A.SelectDecay(vP, vS, decay_select, decay_inv_count); // if A did succeed, stop routine if (ret != EProcessReturn::eOk) { return ret; } } else if constexpr (std::is_base_of_v<DecayProcess<T1type>, T1type>) { // if this is not a ContinuousProcess --> evaluate probability decay_inv_count += A.GetInverseLifetime(vP); // check if we should execute THIS process and then EXIT if (decay_select < decay_inv_count) { // more pedagogical: rndm_select < // decay_inv_count / decay_inv_tot A.DoDecay(vS); return EProcessReturn::eDecayed; } } // end branch A if constexpr (t2ProcSeq) { // if A is a process sequence --> check inside const EProcessReturn ret = B.SelectDecay(vP, vS, decay_select, decay_inv_count); // if A did succeed, stop routine if (ret != EProcessReturn::eOk) { return ret; } } else if constexpr (std::is_base_of_v<DecayProcess<T2type>, T2type>) { // if this is not a ContinuousProcess --> evaluate probability decay_inv_count += B.GetInverseLifetime(vP); // check if we should execute THIS process and then EXIT if (decay_select < decay_inv_count) { B.DoDecay(vS); return EProcessReturn::eDecayed; } } // end branch B return EProcessReturn::eOk; } void Init() { A.Init(); B.Init(); } }; /// the << operator assembles many BaseProcess, ContinuousProcess, and /// Interaction/DecayProcess objects into a ProcessSequence, all combinatorics /// must be allowed, this is why we define a macro to define all /// combinations here: template < typename P1, typename P2, typename std::enable_if<is_process<typename std::decay<P1>::type>::value && is_process<typename std::decay<P2>::type>::value>::type...> inline auto operator<<(P1&& vA, P2&& vB) -> ProcessSequence<P1, P2> { return ProcessSequence<P1, P2>(vA.GetRef(), vB.GetRef()); } /// marker to identify objectas ProcessSequence template <typename A, typename B> struct is_process_sequence<corsika::process::ProcessSequence<A, B>> : std::true_type {}; } // namespace corsika::process #endif