diff --git a/corsika/detail/framework/process/SwitchProcessSequence.inl b/corsika/detail/framework/process/SwitchProcessSequence.inl new file mode 100644 index 0000000000000000000000000000000000000000..6f87779ecb0d46c84a3733b9f2e3997ddab2fb73 --- /dev/null +++ b/corsika/detail/framework/process/SwitchProcessSequence.inl @@ -0,0 +1,291 @@ +/* + * (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/process/BaseProcess.hpp> +#include <corsika/framework/process/ProcessTraits.hpp> +#include <corsika/framework/process/BoundaryCrossingProcess.hpp> +#include <corsika/framework/process/ContinuousProcess.hpp> +#include <corsika/framework/process/DecayProcess.hpp> +#include <corsika/framework/process/InteractionProcess.hpp> +#include <corsika/framework/process/ProcessReturn.hpp> +#include <corsika/framework/process/SecondariesProcess.hpp> +#include <corsika/framework/process/StackProcess.hpp> +#include <corsika/framework/core/PhysicalUnits.hpp> + +#include <cmath> +#include <limits> +#include <type_traits> + +namespace corsika { + + template <typename TProcess1, typename TProcess2, typename TSelect> + template <typename TParticle, typename TVTNType> + EProcessReturn SwitchProcessSequence<TProcess1, TProcess2, TSelect>::doBoundaryCrossing( + TParticle& particle, TVTNType const& from, TVTNType const& to) { + switch (select_(particle)) { + case SwitchResult::First: { + if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process1_type>, + process1_type> || + t1ProcSeq) { + return A_.doBoundaryCrossing(particle, from, to); + } + break; + } + case SwitchResult::Second: { + if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process2_type>, + process2_type> || + t2ProcSeq) { + return B_.doBoundaryCrossing(particle, from, to); + } + break; + } + } + return EProcessReturn::eOk; + } + + template <typename TProcess1, typename TProcess2, typename TSelect> + template <typename TParticle, typename TTrack> + inline EProcessReturn + SwitchProcessSequence<TProcess1, TProcess2, TSelect>::doContinuous(TParticle& particle, + TTrack& vT) { + switch (select_(particle)) { + case SwitchResult::First: { + if constexpr (std::is_base_of_v<ContinuousProcess<process1_type>, + process1_type> || + t1ProcSeq) { + return A_.doContinuous(particle, vT); + } + break; + } + case SwitchResult::Second: { + if constexpr (std::is_base_of_v<ContinuousProcess<process2_type>, + process2_type> || + t2ProcSeq) { + return B_.doContinuous(particle, vT); + } + break; + } + } + return EProcessReturn::eOk; + } + + template <typename TProcess1, typename TProcess2, typename TSelect> + template <typename TSecondaries> + inline void SwitchProcessSequence<TProcess1, TProcess2, TSelect>::doSecondaries( + TSecondaries& vS) { + const auto& particle = vS.parent(); + switch (select_(particle)) { + case SwitchResult::First: { + if constexpr (std::is_base_of_v<SecondariesProcess<process1_type>, + process1_type> || + t1ProcSeq) { + A_.doSecondaries(vS); + } + break; + } + case SwitchResult::Second: { + if constexpr (std::is_base_of_v<SecondariesProcess<process2_type>, + process2_type> || + t2ProcSeq) { + B_.doSecondaries(vS); + } + break; + } + } + } + + template <typename TProcess1, typename TProcess2, typename TSelect> + template <typename TParticle, typename TTrack> + inline LengthType SwitchProcessSequence<TProcess1, TProcess2, TSelect>::maxStepLength( + TParticle& particle, TTrack& vTrack) { + switch (select_(particle)) { + case SwitchResult::First: { + if constexpr (std::is_base_of_v<ContinuousProcess<process1_type>, + process1_type> || + t1ProcSeq) { + return A_.maxStepLength(particle, vTrack); + } + break; + } + case SwitchResult::Second: { + if constexpr (std::is_base_of_v<ContinuousProcess<process2_type>, + process2_type> || + t2ProcSeq) { + return B_.maxStepLength(particle, vTrack); + } + break; + } + } + + // if no other process in the sequence implements it + return std::numeric_limits<double>::infinity() * meter; + } + + template <typename TProcess1, typename TProcess2, typename TSelect> + template <typename TParticle> + inline InverseGrammageType + SwitchProcessSequence<TProcess1, TProcess2, TSelect>::getInverseInteractionLength( + TParticle&& particle) { + + switch (select_(particle)) { + case SwitchResult::First: { + if constexpr (std::is_base_of_v<InteractionProcess<process1_type>, + process1_type> || + t1ProcSeq) { + return A_.getInverseInteractionLength(particle); + } + break; + } + case SwitchResult::Second: { + if constexpr (std::is_base_of_v<InteractionProcess<process2_type>, + process2_type> || + t2ProcSeq) { + return B_.getInverseInteractionLength(particle); + } + break; + } + } + return 0 * meter * meter / gram; // default value + } + + template <typename TProcess1, typename TProcess2, typename TSelect> + template <typename TSecondaryView> + inline EProcessReturn + SwitchProcessSequence<TProcess1, TProcess2, TSelect>::selectInteraction( + TSecondaryView& view, [[maybe_unused]] InverseGrammageType lambda_inv_select, + [[maybe_unused]] InverseGrammageType lambda_inv_sum) { + switch (select_(view.parent())) { + case SwitchResult::First: { + if constexpr (t1ProcSeq) { + // if A_ is a process sequence --> check inside + EProcessReturn 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 != EProcessReturn::eOk) { return ret; } + } else if constexpr (std::is_base_of_v<InteractionProcess<process1_type>, + 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) { + A_.doInteraction(view); + return EProcessReturn::eInteracted; + } + } // end branch A_ + break; + } + + case SwitchResult::Second: { + + if constexpr (t2ProcSeq) { + // if B_ is a process sequence --> check inside + return B_.selectInteraction(view, lambda_inv_select, lambda_inv_sum); + } else if constexpr (std::is_base_of_v<InteractionProcess<process2_type>, + 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) { + B_.doInteraction(view); + return EProcessReturn::eInteracted; + } + } // end branch B_ + break; + } + } + return EProcessReturn::eOk; + } + + template <typename TProcess1, typename TProcess2, typename TSelect> + template <typename TParticle> + inline InverseTimeType + SwitchProcessSequence<TProcess1, TProcess2, TSelect>::getInverseLifetime( + TParticle&& particle) { + + switch (select_(particle)) { + case SwitchResult::First: { + if constexpr (std::is_base_of_v<DecayProcess<process1_type>, process1_type> || + t1ProcSeq) { + return A_.getInverseLifetime(particle); + } + break; + } + + case SwitchResult::Second: { + if constexpr (std::is_base_of_v<DecayProcess<process2_type>, process2_type> || + t2ProcSeq) { + return B_.getInverseLifetime(particle); + } + break; + } + } + return 0 / second; // default value + } + + template <typename TProcess1, typename TProcess2, typename TSelect> + // select decay process + template <typename TSecondaryView> + inline EProcessReturn SwitchProcessSequence<TProcess1, TProcess2, TSelect>::selectDecay( + TSecondaryView& view, [[maybe_unused]] InverseTimeType decay_inv_select, + [[maybe_unused]] InverseTimeType decay_inv_sum) { + switch (select_(view.parent())) { + case SwitchResult::First: { + if constexpr (t1ProcSeq) { + // if A_ is a process sequence --> check inside + EProcessReturn const ret = + A_.selectDecay(view, decay_inv_select, decay_inv_sum); + // if A_ did succeed, stop routine here (not checking other static branch B_) + if (ret != EProcessReturn::eOk) { return ret; } + } else if constexpr (std::is_base_of_v<DecayProcess<process1_type>, + process1_type>) { + // if this is not a ContinuousProcess --> evaluate probability + decay_inv_sum += A_.getInverseLifetime(view.parent()); + // check if we should execute THIS process and then EXIT + if (decay_inv_select < decay_inv_sum) { + // more pedagogical: rndm_select < decay_inv_sum / decay_inv_tot + A_.doDecay(view); + return EProcessReturn::eDecayed; + } + } // end branch A_ + break; + } + + case SwitchResult::Second: { + + if constexpr (t2ProcSeq) { + // if B_ is a process sequence --> check inside + return B_.selectDecay(view, decay_inv_select, decay_inv_sum); + } else if constexpr (std::is_base_of_v<DecayProcess<process2_type>, + process2_type>) { + // if this is not a ContinuousProcess --> evaluate probability + decay_inv_sum += B_.getInverseLifetime(view.parent()); + // check if we should execute THIS process and then EXIT + if (decay_inv_select < decay_inv_sum) { + B_.doDecay(view); + return EProcessReturn::eDecayed; + } + } // end branch B_ + break; + } + } + return EProcessReturn::eOk; + } + + /// traits marker to identify objectas ProcessSequence + template <typename TProcess1, typename TProcess2, typename TSelect> + struct is_process_sequence<SwitchProcessSequence<TProcess1, TProcess2, TSelect>> + : std::true_type {}; + + /// traits marker to identify objectas SwitchProcessSequence + template <typename TProcess1, typename TProcess2, typename TSelect> + struct is_switch_process_sequence<SwitchProcessSequence<TProcess1, TProcess2, TSelect>> + : std::true_type {}; + +} // namespace corsika diff --git a/corsika/framework/process/NullModel.hpp b/corsika/framework/process/NullModel.hpp new file mode 100644 index 0000000000000000000000000000000000000000..51678a977d6a362b7e95310e06edd699a3d196d7 --- /dev/null +++ b/corsika/framework/process/NullModel.hpp @@ -0,0 +1,22 @@ +/* + * (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/process/BaseProcess.hpp> + +namespace corsika { + + class NullModel : public BaseProcess<NullModel> { + + public: + NullModel() = default; + ~NullModel() = default; + }; + +} // namespace corsika diff --git a/corsika/framework/process/ProcessTraits.hpp b/corsika/framework/process/ProcessTraits.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2d3598595f6bdc345c43fa233835ba79611dac75 --- /dev/null +++ b/corsika/framework/process/ProcessTraits.hpp @@ -0,0 +1,43 @@ +/* + * (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 <type_traits> + +namespace corsika { + + /** + * A traits marker to track which BaseProcess is also a ProcessSequence + **/ + template <typename TClass> + struct is_process_sequence : std::false_type {}; + + template <typename TClass> + bool constexpr is_process_sequence_v = is_process_sequence<TClass>::value; + + /** + * A traits marker to identiy a BaseProcess that is also SwitchProcessesSequence + **/ + + template <typename TClass> + struct is_switch_process_sequence : std::false_type {}; + + template <typename TClass> + bool constexpr is_switch_process_sequence_v = is_switch_process_sequence<TClass>::value; + + /** + * A traits marker to identify ProcessSequence that contain a StackProcess + **/ + template <typename TClass> + struct contains_stack_process : std::false_type {}; + + template <typename TClass> + bool constexpr contains_stack_process_v = contains_stack_process<TClass>::value; + +} // namespace corsika diff --git a/corsika/framework/process/SwitchProcessSequence.hpp b/corsika/framework/process/SwitchProcessSequence.hpp new file mode 100644 index 0000000000000000000000000000000000000000..aa772649f771709e27399b115b81ab5d96bc1242 --- /dev/null +++ b/corsika/framework/process/SwitchProcessSequence.hpp @@ -0,0 +1,168 @@ +/* + * (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/process/BaseProcess.hpp> +#include <corsika/framework/process/ProcessTraits.hpp> +#include <corsika/framework/process/BoundaryCrossingProcess.hpp> +#include <corsika/framework/process/ContinuousProcess.hpp> +#include <corsika/framework/process/DecayProcess.hpp> +#include <corsika/framework/process/InteractionProcess.hpp> +#include <corsika/framework/process/ProcessReturn.hpp> +#include <corsika/framework/process/SecondariesProcess.hpp> +#include <corsika/framework/process/StackProcess.hpp> +#include <corsika/framework/core/PhysicalUnits.hpp> + +#include <cmath> +#include <limits> +#include <type_traits> + +namespace corsika { + + // enum for the process switch selection: identify if First or + // Second process branch should be used. + enum class SwitchResult { First, Second }; + + /** + \class SwitchProcessSequence + + A compile time static list of processes that uses an internal + TSelect class to switch between different versions of processes + (or process sequence). + + TProcess1 and TProcess2 must 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 SwitchProcessSequence. + + TSelect has to implement a `operator()(const Particle&)` and has to + return either SwitchResult::First or SwitchResult::Second. Note: + TSelect may absolutely also use random numbers to sample between + its results. This can be used to achieve arbitrarily smooth + transition or mixtures of processes. + + Warning: do not put StackProcess into a SwitchProcessSequence + since this makes no sense. The StackProcess acts on an entire + particle stack and not on indiviidual particles. + + \comment See also class ProcessSequence + **/ + + template <typename TProcess1, typename TProcess2, typename TSelect> + class SwitchProcessSequence + : public BaseProcess<SwitchProcessSequence<TProcess1, TProcess2, TSelect>> { + + using process1_type = typename std::decay_t<TProcess1>; + using process2_type = typename std::decay_t<TProcess2>; + + static bool constexpr t1ProcSeq = is_process_sequence_v<process1_type>; + static bool constexpr t2ProcSeq = is_process_sequence_v<process2_type>; + + // make sure only BaseProcess types TProcess1/2 are passed + static_assert(std::is_base_of_v<BaseProcess<process1_type>, process1_type>, + "can only use process derived from BaseProcess in " + "SwitchProcessSequence, for Process 1"); + static_assert(std::is_base_of_v<BaseProcess<process2_type>, process2_type>, + "can only use process derived from BaseProcess in " + "SwitchProcessSequence, for Process 2"); + + // make sure none of TProcess1/2 is a StackProcess + static_assert(!std::is_base_of_v<StackProcess<process1_type>, process1_type>, + "cannot use StackProcess in SwitchProcessSequence, for Process 1"); + static_assert(!std::is_base_of_v<StackProcess<process2_type>, process2_type>, + "cannot use StackProcess in SwitchProcessSequence, for Process 2"); + + // if TProcess1/2 are already ProcessSequences, make sure they do not contain + // any StackProcess + static_assert(!contains_stack_process_v<process1_type>, + "cannot use StackProcess in SwitchProcessSequence, remove from " + "ProcessSequence 1"); + static_assert(!contains_stack_process_v<process2_type>, + "cannot use StackProcess in SwitchProcessSequence, remove from " + "ProcessSequence 2"); + + TSelect select_; // this is a reference, if possible + + TProcess1 A_; // this is a reference, if possible + TProcess2 B_; // this is a reference, if possible + + public: + SwitchProcessSequence(TProcess1 in_A, TProcess2 in_B, TSelect sel) + : select_(sel) + , A_(in_A) + , B_(in_B) {} + + template <typename TParticle, typename TVTNType> + EProcessReturn doBoundaryCrossing(TParticle& particle, TVTNType const& from, + TVTNType const& to); + + template <typename TParticle, typename TTrack> + inline EProcessReturn doContinuous(TParticle& particle, TTrack& vT); + + template <typename TSecondaries> + inline void doSecondaries(TSecondaries& vS); + + template <typename TParticle, typename TTrack> + inline LengthType maxStepLength(TParticle& particle, TTrack& vTrack); + + template <typename TParticle> + inline GrammageType getInteractionLength(TParticle&& particle) { + return 1. / getInverseInteractionLength(particle); + } + + template <typename TParticle> + inline InverseGrammageType getInverseInteractionLength(TParticle&& particle); + + template <typename TSecondaryView> + inline EProcessReturn selectInteraction( + TSecondaryView& view, [[maybe_unused]] InverseGrammageType lambda_inv_select, + [[maybe_unused]] InverseGrammageType lambda_inv_sum = + InverseGrammageType::zero()); + + template <typename TParticle> + inline TimeType getLifetime(TParticle&& particle) { + return 1. / getInverseLifetime(particle); + } + + template <typename TParticle> + inline InverseTimeType getInverseLifetime(TParticle&& particle); + + // select decay process + template <typename TSecondaryView> + inline EProcessReturn selectDecay( + TSecondaryView& view, [[maybe_unused]] InverseTimeType decay_inv_select, + [[maybe_unused]] InverseTimeType decay_inv_sum = InverseTimeType::zero()); + }; + + /** + * \function make_select + * + * the functin `make_select(proc1,proc1,selector)` assembles many + * BaseProcesses, and ProcessSequences into a SwitchProcessSequence, + * all combinatorics must be allowed, this is why we define a macro + * to define all combinations here: + * + * + * Both, Processes1 and Processes2, must derive from BaseProcesses + **/ + + template <typename TProcess1, typename TProcess2, typename TSelect> + inline typename std::enable_if_t< + std::is_base_of_v<BaseProcess<typename std::decay_t<TProcess1>>, + typename std::decay_t<TProcess1>> && + std::is_base_of_v<BaseProcess<typename std::decay_t<TProcess2>>, + typename std::decay_t<TProcess2>>, + SwitchProcessSequence<TProcess1, TProcess2, TSelect>> + make_select(TProcess1&& vA, TProcess2&& vB, TSelect selector) { + return SwitchProcessSequence<TProcess1, TProcess2, TSelect>(vA, vB, selector); + } + +} // namespace corsika + +#include <corsika/detail/framework/process/SwitchProcessSequence.inl> diff --git a/tests/framework/testNullModel.cpp b/tests/framework/testNullModel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2db1090994525357246ee67ffb0e4cea844c12ab --- /dev/null +++ b/tests/framework/testNullModel.cpp @@ -0,0 +1,23 @@ +/* + * (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. + */ + +#include <corsika/framework/process/NullModel.hpp> + +#include <catch2/catch.hpp> + +using namespace corsika; + +/* + * The NullModel can do really nothing, so we can basically test + * nothing. + */ + +TEST_CASE("NullModel", "[processes]") { + + SECTION("interface") { [[maybe_unused]] NullModel model; } +}