diff --git a/corsika/detail/framework/process/InteractionProcess.hpp b/corsika/detail/framework/process/InteractionProcess.hpp index ed67fed3df2c7a8cbe9054ac08d83f5c6d5411e0..f97fd4e4e9534241fe378f80814f37fd860502ad 100644 --- a/corsika/detail/framework/process/InteractionProcess.hpp +++ b/corsika/detail/framework/process/InteractionProcess.hpp @@ -1,46 +1,35 @@ -#pragma once - -namespace corsika { - - namespace detail { - - /** - Helper traits class (partial) for static compile time checking. - - Note, this is a poor replacement for C++20 concepts... they are - eagerly awaited! - - It defines the default body of a generic test function returning - std::false_type. - - In addition it defines the pattern for class-method matching with a - return type TReturn and function arguments TArgs... . Right now - both method signatures, "const" and "not const", are matched. - */ - template <typename TReturn, typename... TArgs> - struct has_method_signature { +/* + * (c) Copyright 2021 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. + */ - template <class T> - static std::true_type testSignature(TReturn (T::*)(TArgs&&...)); +#pragma once - template <class T> - static std::true_type testSignature(TReturn (T::*)(TArgs&&...) const); +#include <corsika/framework/process/ProcessTraits.hpp> - template <class T> - static std::false_type test(...); - }; +namespace corsika { - } // namespace detail + // doInteract + template <class TProcess, typename TReturn, typename... TArgs> struct has_method_doInteract : public detail::has_method_signature<TReturn, TArgs...> { using detail::has_method_signature<TReturn, TArgs...>::testSignature; + // the default value + template <class T> + static std::false_type test(...); + + // signature of templated method template <class T> static decltype(testSignature(&T::template doInteraction<TArgs...>)) test( std::nullptr_t); + // signature of non-templated method template <class T> static decltype(testSignature(&T::doInteraction)) test(std::nullptr_t); @@ -48,17 +37,24 @@ namespace corsika { using type = decltype(test<std::decay_t<TProcess>>(nullptr)); static const bool value = type::value; }; - + template <class TProcess, typename TReturn, typename... TArgs> bool constexpr has_method_doInteract_v = has_method_doInteract<TProcess, TReturn, TArgs...>::value; + + // getInteractionLength + template <class TProcess, typename TReturn, typename... TArgs> struct has_method_getInteractionLength : public detail::has_method_signature<TReturn, TArgs...> { using detail::has_method_signature<TReturn, TArgs...>::testSignature; + // the default value + template <class T> + static std::false_type test(...); + template <class T> static decltype(testSignature(&T::template getInteractionLength<TArgs...>)) test( std::nullptr_t); diff --git a/corsika/detail/framework/process/ProcessSequence.inl b/corsika/detail/framework/process/ProcessSequence.inl index d1ba0a0ce4723fb64aa554912eaa1456d88f9dd8..45117307dfdfac8683f7671816c1d03ada821e5b 100644 --- a/corsika/detail/framework/process/ProcessSequence.inl +++ b/corsika/detail/framework/process/ProcessSequence.inl @@ -39,12 +39,39 @@ namespace corsika { if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process1_type>, process1_type> || t1ProcSeq) { + + if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process1_type>, + process1_type>) { + + // interface checking on TProcess1 + static_assert( + has_method_doBoundaryCrossing_v<TProcess1, ProcessReturn, TParticle&, + typename TParticle::node_type const&, + typename TParticle::node_type const&>, + "TDerived has no method with correct signature \"ProcessReturn " + "doBoundaryCrossing(TParticle&, VolumeNode&, VolumeNode&)\" required for " + "BoundaryCrossingProcess<TDerived>. "); + } + ret |= A_.doBoundaryCrossing(particle, from, to); } if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process2_type>, process2_type> || t2ProcSeq) { + + if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process2_type>, + process2_type>) { + // interface checking on TProcess2 + static_assert( + has_method_doBoundaryCrossing_v<TProcess2, ProcessReturn, TParticle&, + typename TParticle::node_type const&, + typename TParticle::node_type const&>, + "TDerived has no method with correct signature \"ProcessReturn " + "doBoundaryCrossing(TParticle&, VolumeNode&, VolumeNode&)\" required for " + "BoundaryCrossingProcess<TDerived>. "); + } + ret |= B_.doBoundaryCrossing(particle, from, to); } @@ -60,8 +87,17 @@ namespace corsika { [[maybe_unused]] ContinuousProcessIndex const limitId) { ProcessReturn ret = ProcessReturn::Ok; if constexpr (t1ProcSeq) { + ret |= A_.doContinuous(particle, vT, limitId); } else if constexpr (is_continuous_process_v<process1_type>) { + + // interface checking on TProcess1 + static_assert( + has_method_doContinuous_v<TProcess1, ProcessReturn, TParticle&, TTrack&>, + "TDerived has no method with correct signature \"ProcessReturn " + "doContinuous(TParticle&,TTrack&,bool)\" required for " + "ContinuousProcess<TDerived>. "); + ret |= A_.doContinuous(particle, vT, limitId == ContinuousProcessIndex(IndexProcess1)); } @@ -69,6 +105,14 @@ namespace corsika { if constexpr (t2ProcSeq) { ret |= B_.doContinuous(particle, vT, limitId); } else if constexpr (is_continuous_process_v<process2_type>) { + + // interface checking on TProcess2 + static_assert( + has_method_doContinuous_v<TProcess2, ProcessReturn, TParticle&, TTrack&>, + "TDerived has no method with correct signature \"ProcessReturn " + "doContinuous(TParticle&,TTrack&,bool)\" required for " + "ContinuousProcess<TDerived>. "); + ret |= B_.doContinuous(particle, vT, limitId == ContinuousProcessIndex(IndexProcess2)); } @@ -83,10 +127,24 @@ namespace corsika { IndexProcess2>::doSecondaries(TSecondaries& vS) { if constexpr (std::is_base_of_v<SecondariesProcess<process1_type>, process1_type> || t1ProcSeq) { + + // interface checking on TProcess1 + static_assert(has_method_doSecondaries_v<TProcess1, void, TSecondaries&>, + "TDerived has no method with correct signature \"void " + "doSecondaries(TStackView&)\" required for " + "SecondariesProcessProcess<TDerived>. "); + A_.doSecondaries(vS); } if constexpr (std::is_base_of_v<SecondariesProcess<process2_type>, process2_type> || t2ProcSeq) { + + // interface checking on TProcess2 + static_assert(has_method_doSecondaries_v<TProcess2, void, TSecondaries&>, + "TDerived has no method with correct signature \"void " + "doSecondaries(TStackView&)\" required for " + "SecondariesProcessProcess<TDerived>. "); + B_.doSecondaries(vS); } } @@ -114,10 +172,26 @@ namespace corsika { IndexProcess2>::doStack(TStack& stack) { if constexpr (std::is_base_of_v<StackProcess<process1_type>, process1_type> || (t1ProcSeq && !t1SwitchProcSeq)) { + + // interface checking on TProcess1 + static_assert(has_method_doStack_v<TProcess1, void, TStack&> || + has_method_doStack_v<TProcess1, void, TStack const&>, + "TDerived has no method with correct signature \"void " + "doStack(TStackx&)\" required for " + "StackProcess<TDerived>. "); + if (A_.checkStep()) { A_.doStack(stack); } } if constexpr (std::is_base_of_v<StackProcess<process2_type>, process2_type> || (t2ProcSeq && !t2SwitchProcSeq)) { + + // interface checking on TProcess1 + static_assert(has_method_doStack_v<TProcess2, void, TStack&> || + has_method_doStack_v<TProcess2, void, TStack const&>, + "TDerived has no method with correct signature \"void " + "doStack(TStack&)\" required for " + "StackProcess<TDerived>. "); + if (B_.checkStep()) { B_.doStack(stack); } } } @@ -136,6 +210,14 @@ namespace corsika { ContinuousProcessStepLength const step = A_.getMaxStepLength(particle, vTrack); max_length = std::min(max_length, step); } else if constexpr (is_continuous_process_v<process1_type>) { + + // interface checking on TProcess1 + static_assert( + has_method_getMaxStepLength_v<TProcess1, LengthType, TParticle&, TTrack&>, + "TDerived has no method with correct signature \"Grammage " + "getMaxStepLength(TParticle&, TTrack&)\" required for " + "ContinuousProcess<TDerived>. "); + ContinuousProcessStepLength const step(A_.getMaxStepLength(particle, vTrack), ContinuousProcessIndex(IndexProcess1)); max_length = std::min(max_length, step); @@ -145,6 +227,14 @@ namespace corsika { ContinuousProcessStepLength const step = B_.getMaxStepLength(particle, vTrack); max_length = std::min(max_length, step); } else if constexpr (is_continuous_process_v<process2_type>) { + + // interface checking on TProcess2 + static_assert( + has_method_getMaxStepLength_v<TProcess2, LengthType, TParticle&, TTrack&>, + "TDerived has no method with correct signature \"Grammage " + "getMaxStepLength(TParticle&, TTrack&)\" required for " + "ContinuousProcess<TDerived>. "); + ContinuousProcessStepLength const step(B_.getMaxStepLength(particle, vTrack), ContinuousProcessIndex(IndexProcess2)); max_length = std::min(max_length, step); @@ -274,6 +364,12 @@ namespace corsika { // 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 + // interface checking on TProcess1 + static_assert(has_method_doDecay_v<TProcess1, void, TSecondaryView&>, + "TDerived has no method with correct signature \"void " + "doDecay(TSecondaryView&)\" required for " + "DecayProcess<TDerived>. "); + A_.doDecay(view); return ProcessReturn::Decayed; } @@ -287,6 +383,14 @@ namespace corsika { decay_inv_sum += B_.getInverseLifetime(view.parent()); // check if we should execute THIS process and then EXIT if (decay_inv_select <= decay_inv_sum) { + + // interface checking on TProcess1 + + static_assert(has_method_doDecay_v<TProcess2, void, TSecondaryView&>, + "TDerived has no method with correct signature \"void " + "doDecay(TSecondaryView&)\" required for " + "DecayProcess<TDerived>. "); + B_.doDecay(view); return ProcessReturn::Decayed; } diff --git a/corsika/detail/framework/process/SwitchProcessSequence.inl b/corsika/detail/framework/process/SwitchProcessSequence.inl index c4f8ede12f859e7c063f91cabbcc01af8b99cbc4..1a6d4d89df92d63bdeaad07744a95fffad300062 100644 --- a/corsika/detail/framework/process/SwitchProcessSequence.inl +++ b/corsika/detail/framework/process/SwitchProcessSequence.inl @@ -40,6 +40,20 @@ namespace corsika { if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process1_type>, process1_type> || t1ProcSeq) { + + // interface checking on TProcess1 + if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process1_type>, + process1_type>) { + + static_assert( + has_method_doBoundaryCrossing_v<TProcess1, ProcessReturn, TParticle&, + typename TParticle::node_type const&, + typename TParticle::node_type const&>, + "TDerived has no method with correct signature \"ProcessReturn " + "doBoundaryCrossing(TParticle&, VolumeNode&, VolumeNode&)\" required for " + "BoundaryCrossingProcess<TDerived>. "); + } + return A_.doBoundaryCrossing(particle, from, to); } break; @@ -48,6 +62,20 @@ namespace corsika { if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process2_type>, process2_type> || t2ProcSeq) { + + // interface checking on TProcess2 + if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process2_type>, + process2_type>) { + + static_assert( + has_method_doBoundaryCrossing_v<TProcess2, ProcessReturn, TParticle&, + typename TParticle::node_type const&, + typename TParticle::node_type const&>, + "TDerived has no method with correct signature \"ProcessReturn " + "doBoundaryCrossing(TParticle&, VolumeNode&, VolumeNode&)\" required for " + "BoundaryCrossingProcess<TDerived>. "); + } + return B_.doBoundaryCrossing(particle, from, to); } break; @@ -67,6 +95,13 @@ namespace corsika { case SwitchResult::First: { if constexpr (t1ProcSeq) { return A_.doContinuous(particle, vT, idLimit); } if constexpr (is_continuous_process_v<process1_type>) { + + static_assert( + has_method_doContinuous_v<TProcess1, ProcessReturn, TParticle&, TTrack&>, + "TDerived has no method with correct signature \"ProcessReturn " + "doContinuous(TParticle&,TTrack&,bool)\" required for " + "ContinuousProcess<TDerived>. "); + return A_.doContinuous(particle, vT, idLimit == ContinuousProcessIndex(IndexProcess1)); } @@ -75,6 +110,14 @@ namespace corsika { case SwitchResult::Second: { if constexpr (t2ProcSeq) { return B_.doContinuous(particle, vT, idLimit); } if constexpr (is_continuous_process_v<process2_type>) { + + // interface checking on TProcess2 + static_assert( + has_method_doContinuous_v<TProcess2, ProcessReturn, TParticle&, TTrack&>, + "TDerived has no method with correct signature \"ProcessReturn " + "doContinuous(TParticle&,TTrack&,bool)\" required for " + "ContinuousProcess<TDerived>. "); + return B_.doContinuous(particle, vT, idLimit == ContinuousProcessIndex(IndexProcess2)); } @@ -96,6 +139,13 @@ namespace corsika { if constexpr (std::is_base_of_v<SecondariesProcess<process1_type>, process1_type> || t1ProcSeq) { + + // interface checking on TProcess1 + static_assert(has_method_doSecondaries_v<TProcess1, void, TSecondaries&>, + "TDerived has no method with correct signature \"void " + "doSecondaries(TStackView&)\" required for " + "SecondariesProcessProcess<TDerived>. "); + A_.doSecondaries(vS); } break; @@ -104,6 +154,13 @@ namespace corsika { if constexpr (std::is_base_of_v<SecondariesProcess<process2_type>, process2_type> || t2ProcSeq) { + + // interface checking on TProcess2 + static_assert(has_method_doSecondaries_v<TProcess2, void, TSecondaries&>, + "TDerived has no method with correct signature \"void " + "doSecondaries(TStackView&)\" required for " + "SecondariesProcessProcess<TDerived>. "); + B_.doSecondaries(vS); } break; @@ -122,6 +179,14 @@ namespace corsika { case SwitchResult::First: { if constexpr (t1ProcSeq) { return A_.getMaxStepLength(particle, vTrack); } if constexpr (is_continuous_process_v<process1_type>) { + + // interface checking on TProcess1 + static_assert( + has_method_getMaxStepLength_v<TProcess1, LengthType, TParticle&, TTrack&>, + "TDerived has no method with correct signature \"Grammage " + "getMaxStepLength(TParticle&, TTrack&)\" required for " + "ContinuousProcess<TDerived>. "); + return ContinuousProcessStepLength(A_.getMaxStepLength(particle, vTrack), ContinuousProcessIndex(IndexProcess1)); } @@ -130,6 +195,14 @@ namespace corsika { case SwitchResult::Second: { if constexpr (t2ProcSeq) { return B_.getMaxStepLength(particle, vTrack); } if constexpr (is_continuous_process_v<process2_type>) { + + // interface checking on TProcess2 + static_assert( + has_method_getMaxStepLength_v<TProcess2, LengthType, TParticle&, TTrack&>, + "TDerived has no method with correct signature \"Grammage " + "getMaxStepLength(TParticle&, TTrack&)\" required for " + "ContinuousProcess<TDerived>. "); + return ContinuousProcessStepLength(B_.getMaxStepLength(particle, vTrack), ContinuousProcessIndex(IndexProcess2)); } @@ -191,6 +264,13 @@ namespace corsika { 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>. "); + A_.doInteraction(view); return ProcessReturn::Interacted; } @@ -209,6 +289,13 @@ namespace corsika { 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 TProcess1 + static_assert(has_method_doInteract_v<TProcess2, void, TSecondaryView&>, + "TDerived has no method with correct signature \"void " + "doInteraction(TSecondaryView&)\" required for " + "InteractionProcess<TDerived>. "); + B_.doInteraction(view); return ProcessReturn::Interacted; } @@ -269,6 +356,13 @@ namespace corsika { // 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 + + // interface checking on TProcess1 + static_assert(has_method_doDecay_v<TProcess1, void, TSecondaryView&>, + "TDerived has no method with correct signature \"void " + "doDecay(TSecondaryView&)\" required for " + "DecayProcess<TDerived>. "); + A_.doDecay(view); return ProcessReturn::Decayed; } @@ -287,6 +381,13 @@ namespace corsika { decay_inv_sum += B_.getInverseLifetime(view.parent()); // check if we should execute THIS process and then EXIT if (decay_inv_select < decay_inv_sum) { + + // interface checking on TProcess1 + static_assert(has_method_doDecay_v<TProcess2, void, TSecondaryView&>, + "TDerived has no method with correct signature \"void " + "doDecay(TSecondaryView&)\" required for " + "DecayProcess<TDerived>. "); + B_.doDecay(view); return ProcessReturn::Decayed; } @@ -297,14 +398,6 @@ namespace corsika { return ProcessReturn::Ok; } - /* - /// traits marker to identify objectas ProcessSequence - template <typename TProcess1, typename TProcess2, typename TSelect> - struct is_process_sequence<ProcessSequence<typename std::decay_t<TProcess1>, - typename std::decay_t<TProcess2>, - typename std::decay_t<TSelect>>> - : std::true_type {}; - */ /// traits marker to identify objectas ProcessSequence template <typename TProcess1, typename TProcess2, typename TSelect> struct is_process_sequence<SwitchProcessSequence<TProcess1, TProcess2, TSelect>> diff --git a/corsika/framework/process/BaseProcess.hpp b/corsika/framework/process/BaseProcess.hpp index 5a12986a0cb196a1dda607e18db548fc147fac04..47f2a8eaabcd8d034b16940b279ada6627807201 100644 --- a/corsika/framework/process/BaseProcess.hpp +++ b/corsika/framework/process/BaseProcess.hpp @@ -12,6 +12,8 @@ #include <type_traits> +//! @file BaseProcess.hpp + namespace corsika { class TDerived; // fwd decl @@ -28,8 +30,8 @@ namespace corsika { ProcessSequence. Both, the ProcessSequence and all its elements are of type BaseProcess - \todo rename BaseProcess into just Process - \todo rename _BaseProcess, or find better alternative in FIXME + @todo rename BaseProcess into just Process + @todo rename _BaseProcess, or find better alternative in FIXME ./Processes/AnalyticProcessors/ExecTime.h, see e.g. how this is done in ProcessSequence.hpp/make_sequence */ @@ -43,20 +45,24 @@ namespace corsika { // derived classes to be created, not // BaseProcess itself + /** @name getRef Return reference to underlying type + @{ + */ TDerived& ref() { return static_cast<TDerived&>(*this); } const TDerived& ref() const { return static_cast<const TDerived&>(*this); } + //! @} public: - //! Default number of processes ist just one, obviously + //! Default number of processes is just one, obviously static unsigned int constexpr getNumberOfProcesses() { return 1; } - // Base processor type for use in other template classes + //! Base processor type for use in other template classes using process_type = TDerived; }; /** - ProcessTraits specialization - **/ + is_process traits specialization to indicate inheritance from BaseProcess + */ template <typename TProcess> struct is_process< TProcess, @@ -64,6 +70,9 @@ namespace corsika { typename std::decay_t<TProcess>>>> : std::true_type {}; + /** + count_processes traits specialization to increase process count by one. + */ template <typename TProcess, int N> struct count_processes<TProcess, N, typename std::enable_if_t<is_process_v<TProcess> && diff --git a/corsika/framework/process/BoundaryCrossingProcess.hpp b/corsika/framework/process/BoundaryCrossingProcess.hpp index 2ea1c1c186d308ce7f31f82c3aad18ef4c516c8b..04410676248bae89f7b1ee995b3fec78bceec812 100644 --- a/corsika/framework/process/BoundaryCrossingProcess.hpp +++ b/corsika/framework/process/BoundaryCrossingProcess.hpp @@ -13,29 +13,39 @@ #include <type_traits> +#include <corsika/detail/framework/process/BoundaryCrossingProcess.hpp> // for extra traits, method/interface checking + namespace corsika { - /** @ingroup Processes - @{ - */ + /** + @ingroup Processes + @{ + + Processes acting on the particles traversion from one volume into + another volume. + + Create a new BoundaryCrossingProcess, e.g. for XYModel, via + @code{.cpp} + class XYModel : public BoundaryCrossingProcess<XYModel> {}; + @endcode + + and provide the necessary interface method: + @code{.cpp} + template <typename TParticle> + ProcessReturn XYModel::doBoundaryCrossing(TParticle& Particle, + typename TParticle::node_type const& from, + typename TParticle::node_type const& to); + @endcode + + where Particle is the object to read particle data from a + Stack. The volume the particle is originating from is `from`, the + volume where it goes to is `to`. + */ template <typename TDerived> class BoundaryCrossingProcess : public BaseProcess<TDerived> { - - /* static_assert(std::is_invocable_v<decltype(&TDerived<>::doBoundaryCrossing), - TDerived&, passepartout>, "BoundaryCrossingProcess needs - doBoundaryCrossing(TParticle, " "TParticle::node_type, TParticle::node_type)");*/ - public: - /** - * This method is called when a particle crosses the boundary between the nodes - * \p from and \p to. - */ - template <typename TParticle> - ProcessReturn doBoundaryCrossing(TParticle&, - typename TParticle::node_type const& from, - typename TParticle::node_type const& to); }; //! @} diff --git a/corsika/framework/process/ContinuousProcess.hpp b/corsika/framework/process/ContinuousProcess.hpp index 23321bfe6e73e3d642b1fc174a7f54cddd625ea5..e8280f78c48419ceb0dd4faac5eb10d90f2cc4f8 100644 --- a/corsika/framework/process/ContinuousProcess.hpp +++ b/corsika/framework/process/ContinuousProcess.hpp @@ -13,6 +13,8 @@ #include <corsika/framework/process/ProcessReturn.hpp> #include <corsika/framework/process/ProcessTraits.hpp> +#include <corsika/detail/framework/process/ContinuousProcess.hpp> // for extra traits, method/interface checking + namespace corsika { /** @@ -21,41 +23,50 @@ namespace corsika { Processes with continuous effects along a particle Trajectory - The structural base type of a process object in a - ProcessSequence. Both, the ProcessSequence and all its elements - are of type ContinuousProcess<T> + Create a new ContinuousProcess, e.g. for XYModel, via + @code{.cpp} + class XYModel : public ContinuousProcess<XYModel> {}; + @endcode + + and provide two necessary interface methods: + @code{.cpp} + template <typename TParticle, typename TTrack> + LengthType getMaxStepLength(TParticle const& p, TTrack const& track) const; + @endcode + + which allows any ContinuousProcess to tell to CORSIKA a maximum + allowed step length. Such step-length limitation, if it turns out + to be smaller/sooner than any other limit (decay length, + interaction length, other continuous processes, geometry, etc.) + will lead to a limited step length. + + @code{.cpp} + template <typename TParticle, typename TTrack> + ProcessReturn doContinuous(TParticle& p, TTrack const& t, bool const stepLimit) const; + @endcode + + which applied any continuous effects on Particle p along + Trajectory t. The particle in all typical scenarios will be + altered by a doContinuous. The flag stepLimit will be true if the + preious evaluation of getMaxStepLength resulted in this + particular ContinuousProcess to be responsible for the step + length limit on the current track t. This information can be + expoited and avoid e.g. any uncessary calculations. + + Particle and Track are the valid classes to + access particles and track (Trajectory) data on the Stack. 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. */ template <typename TDerived> class ContinuousProcess : public BaseProcess<TDerived> { - private: - protected: - public: - // here starts the interface part - /** - * Applies the effects of this ContinuousProcess on a Particle on a Track. - * - * Note, the stepLimit is a flag, if this particular process was responsible for the - * track-length limit. This can be used by the process to trigger activity. - * - * \todo -> enforce TDerived to implement doContinuous... - **/ - template <typename TParticle, typename TTrack> - ProcessReturn doContinuous(TParticle&, TTrack const&, bool const stepLimit) const; - - /** - * Calculates/returns a possible step length limitation of this continuousprocess. - * - * - * \todo -> enforce TDerived to implement getMaxStepLength... - **/ - template <typename TParticle, typename TTrack> - LengthType getMaxStepLength(TParticle const& p, TTrack const& track) const; }; /** - * ProcessTraits specialization + * ProcessTraits specialization to flag ContinuousProcess objects **/ template <typename TProcess> struct is_continuous_process< @@ -64,12 +75,6 @@ namespace corsika { typename std::decay_t<TProcess>>>> : std::true_type {}; - template <typename TProcess, int N> - struct count_continuous<TProcess, N, - typename std::enable_if_t<is_continuous_process_v<TProcess>>> { - enum { count = N + 1 }; - }; - /** @} */ } // namespace corsika diff --git a/corsika/framework/process/DecayProcess.hpp b/corsika/framework/process/DecayProcess.hpp index 198cafe059388a67696fb4eeb08e3c6cdd5db3ca..d2ff357dde90d1c1adb5b3171a168d12fc480ff5 100644 --- a/corsika/framework/process/DecayProcess.hpp +++ b/corsika/framework/process/DecayProcess.hpp @@ -11,6 +11,8 @@ #include <corsika/framework/process/BaseProcess.hpp> #include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/detail/framework/process/DecayProcess.hpp> // for extra traits, method/interface checking + namespace corsika { /** @@ -19,10 +21,30 @@ namespace corsika { Process decribing the decay of particles - The structural base type of a process object in a - ProcessSequence. Both, the ProcessSequence and all its elements - are of type DecayProcess<T> + Create a new DecayProcess, e.g. for XYModel, via + @code + class XYModel : public DecayProcess<XYModel> {}; + @endcode + + and provide the two necessary interface methods + @code + template <typename TSecondaryView> + void XYModel::doDecay(TSecondaryView&); + template <typename TParticle> + TimeType getLifetime(TParticle const&) + @endcode + + Where, of course, SecondaryView and Particle are the valid + classes to access particles on the Stack. 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 decays can be studied right away. */ template <typename TDerived> @@ -30,16 +52,15 @@ namespace corsika { public: using BaseProcess<TDerived>::ref; - /// here starts the interface-definition part - // -> enforce TDerived to implement DoDecay... template <typename TParticle> - void doDecay(TParticle&); + InverseTimeType getInverseLifetime(TParticle const& particle) { - template <typename TParticle> - TimeType getLifetime(TParticle const&); + // interface checking on TProcess1 + static_assert(has_method_getLifetime_v<TDerived, TimeType, TParticle const&>, + "TDerived has no method with correct signature \"GrammageType " + "getInteractionLength(TParticle const&)\" required for " + "InteractionProcess<TDerived>. "); - template <typename TParticle> - InverseTimeType getInverseLifetime(TParticle const& particle) { return 1. / ref().getLifetime(particle); } }; diff --git a/corsika/framework/process/InteractionProcess.hpp b/corsika/framework/process/InteractionProcess.hpp index 1cbddf005b62e8016737d7073ce1e226deba15d0..614224f0b08f8003d485fb0619d6543f77059f1a 100644 --- a/corsika/framework/process/InteractionProcess.hpp +++ b/corsika/framework/process/InteractionProcess.hpp @@ -21,22 +21,19 @@ namespace corsika { Process describing the interaction of particles - The structural base type for any interaction process in a - ProcessSequence. - Create a new InteractionProcess, e.g. for XYModel, via - \code + @code class XYModel : public InteractionProcess<XYModel> {}; - \endcode + @endcode and provide the two necessary interface methods - \code + @code template <typename TSecondaryView> - void doInteraction(TSecondaryView&); + void XYModel::doInteraction(TSecondaryView&); template <typename TParticle> - GrammageType getInteractionLength(TParticle const&) - \endcode + GrammageType XYModel::getInteractionLength(TParticle const&) + @endcode Where, of course, SecondaryView and Particle are the valid classes to access particles on the Stack. Those two methods do @@ -44,8 +41,11 @@ namespace corsika { e.g. corsika::setup::Stack::particle_type -- but by the cost of loosing all flexibility otherwise provided. - (For your potential interest, InteractionProcess is based on the - CRTP C++ design pattern) + 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> diff --git a/corsika/framework/process/ProcessSequence.hpp b/corsika/framework/process/ProcessSequence.hpp index c660ab7d0c33fb5e8fb6b811b3dca055f44cfe2d..0c375beecb583da4cdce7bb61c728bc0a1e3d620 100644 --- a/corsika/framework/process/ProcessSequence.hpp +++ b/corsika/framework/process/ProcessSequence.hpp @@ -28,15 +28,11 @@ namespace corsika { - // traits class to statically count processes in the sequence - template <typename TProcess, int N> - struct count_continuous<TProcess, N, - typename std::enable_if_t<is_process_sequence_v<TProcess>>> { - static unsigned int constexpr count = - N + std::decay_t<TProcess>::getNumberOfProcesses(); - }; - // traits class 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, typename std::enable_if_t<is_process_v<TProcess> && @@ -122,10 +118,6 @@ namespace corsika { @ingroup Processes @{ - */ - - - /** Definition of a static process list/sequence @@ -138,13 +130,16 @@ namespace corsika { they are just classes. This allows us to handle both, rvalue as well as lvalue Processes in the ProcessSequence. - (The sequence, and the processes use the CRTP, curiously recurring template - pattern). + (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 ProcessIndexOffset to count and index each ContinuousProcess in the entire process-chain + @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 ProcessIndexOffset to count and index each ContinuousProcess in the entire process-chain @tparam IndexOfProcess1 @tparam IndexOfProcess2 */ diff --git a/corsika/framework/process/ProcessTraits.hpp b/corsika/framework/process/ProcessTraits.hpp index b08318093e7be4147e5408661b16c8feb1c3921e..74a301e2c451023626faafa4d398157d063cf812 100644 --- a/corsika/framework/process/ProcessTraits.hpp +++ b/corsika/framework/process/ProcessTraits.hpp @@ -9,7 +9,7 @@ #pragma once /** - * \file ProcessTraits.hpp + * @file ProcessTraits.hpp */ #include <type_traits> @@ -62,14 +62,6 @@ namespace corsika { template <typename TClass> bool constexpr contains_stack_process_v = contains_stack_process<TClass>::value; - /** - * traits class to count ContinuousProcess-es, general version - **/ - template <typename TProcess, int N = 0, typename Enable = void> - struct count_continuous { - static unsigned int constexpr count = N; - }; - /** * traits class to count any type of Process, general version **/ @@ -78,4 +70,34 @@ namespace corsika { static unsigned int constexpr count = N; }; + namespace detail { + + /** + Helper traits class (partial) for static compile time checking. + + Note, this is a poor replacement for C++20 concepts... they are + eagerly awaited! + + It defines the default body of a generic test function returning + std::false_type. + + In addition it defines the pattern for class-method matching with a + return type TReturn and function arguments TArgs... . Right now + both method signatures, "const" and "not const", are matched. + */ + template <typename TReturn, typename... TArgs> + struct has_method_signature { + + // the non-const version + template <class T> + static std::true_type testSignature(TReturn (T::*)(TArgs...)); + + // the const version + template <class T> + static std::true_type testSignature(TReturn (T::*)(TArgs...) const); + + }; + + } // namespace detail + } // namespace corsika diff --git a/corsika/framework/process/SecondariesProcess.hpp b/corsika/framework/process/SecondariesProcess.hpp index 2146ba5a03862d79145757ae5dfb006930fd7480..ed0b92662b2b35b7b2883dc959450a5e303acb0e 100644 --- a/corsika/framework/process/SecondariesProcess.hpp +++ b/corsika/framework/process/SecondariesProcess.hpp @@ -11,25 +11,36 @@ #include <corsika/framework/process/BaseProcess.hpp> #include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/detail/framework/process/SecondariesProcess.hpp> // for extra traits, method/interface checking + namespace corsika { /** @ingroup Processes @{ - Process that modifies a list of secondaries of other processes - The structural base type of a process object in a - ProcessSequence. Both, the ProcessSequence and all its elements - are of type SecondariesProcess<T> + Processes acting on the secondaries produced by other processes. + + Create a new SecondariesProcess, e.g. for XYModel, via + @code{.cpp} + class XYModel : public SecondariesProcess<XYModel> {}; + @endcode + + and provide the necessary interface method: + @code{.cpp} + template <typename TStackView> + void doSecondaries(TStackView& StackView); + @endcode + + where StackView is an object that can store secondaries on a + stack and also iterate over these secondaries. */ template <typename TDerived> class SecondariesProcess : public BaseProcess<TDerived> { public: - /// here starts the interface-definition part - // -> enforce TDerived to implement DoSecondaries... - template <typename TSecondaries> - void doSecondaries(TSecondaries&); + + static }; //! @} diff --git a/corsika/framework/process/StackProcess.hpp b/corsika/framework/process/StackProcess.hpp index d1a748cd0d47781000e9bfae126e3a98d339cfe3..752040bf0639c912ed059e5ea6974c2607f41681 100644 --- a/corsika/framework/process/StackProcess.hpp +++ b/corsika/framework/process/StackProcess.hpp @@ -11,18 +11,37 @@ #include <corsika/framework/process/BaseProcess.hpp> #include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/detail/framework/process/StackProcess.hpp> // for extra traits, method/interface checking + namespace corsika { /** - @ingroup Processes + @ingroup Processes @{ Process to act on the entire particle stack - The structural base type of a process object in a - ProcessSequence. Both, the ProcessSequence and all its elements - are of type StackProcess<T> + Create a new StackProcess, e.g. for XYModel, via + @code{.cpp} + class XYModel : public StackProcess<XYModel> {}; + @endcode + + and provide the necessary interface method + @code{.cpp} + template <typename TStack> + void XYModel::doStack(TStack&); + @endcode + + Where, of course, Stack is the valid + class to access particles on the Stack. This methods does + not need to be templated, they could use the types + e.g. corsika::setup::Stack directly -- but by the cost of + loosing all flexibility otherwise provided. + A StackProcess has only one constructor `StackProcess::StackProcess(unsigned int const nStep)` + where nStep is the number of steps of the cascade stepping after which the stack process should be + run. Good values are on the order of 1000, which will not compromise run time in the end, but + provide all the benefits of the StackProcess. */ template <typename TDerived> @@ -34,19 +53,17 @@ namespace corsika { StackProcess(const unsigned int nStep) : nStep_(nStep) {} - /// here starts the interface-definition part - // -> enforce TDerived to implement DoStack... - template <typename TStack> - void doStack(TStack&); - + //! return the current Cascade step counter int getStep() const { return iStep_; } + + //! check if current step is where StackProcess should be executed, this also increases the internal step counter implicitly bool checkStep() { return !((++iStep_) % nStep_); } private: /** @name The number of "steps" during the cascade processing after which this StackProcess is going to be executed. The logic is - "fIStep modulo fNStep" + "iStep_ modulo nStep_" @{ */ unsigned int nStep_ = 0; diff --git a/corsika/framework/process/SwitchProcessSequence.hpp b/corsika/framework/process/SwitchProcessSequence.hpp index 453446981f5f544450110b8fa6e4f5c068ad4fd5..585f55d4eeeefda031e5e10cb334e610b96475e2 100644 --- a/corsika/framework/process/SwitchProcessSequence.hpp +++ b/corsika/framework/process/SwitchProcessSequence.hpp @@ -69,12 +69,12 @@ namespace corsika { particle stack and not on indiviidual particles. 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 TSelect selector functor/function - @tparam IndexFirstProcess to count and index each ContinuousProcess in the entire process-chain - @tparam IndexOfProcess1 index of TProcess1 (counting of ContinuousProcess) - @tparam IndexOfProcess2 index of TProcess2 (counting of ContinuousProcess) + @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 TSelect selector functor/function + @tparam IndexFirstProcess to count and index each ContinuousProcess in the entire process-chain + @tparam IndexOfProcess1 index of TProcess1 (counting of ContinuousProcess) + @tparam IndexOfProcess2 index of TProcess2 (counting of ContinuousProcess) See also class ProcessSequence. **/ diff --git a/corsika/modules/StackInspector.hpp b/corsika/modules/StackInspector.hpp index 9616079540a21792148f54186bbe5890a1400b43..174e8e05c335af7e4c5f89ed4e621767a13abea6 100644 --- a/corsika/modules/StackInspector.hpp +++ b/corsika/modules/StackInspector.hpp @@ -26,7 +26,7 @@ namespace corsika { StackInspector(const int vNStep, const bool vReportStack, const HEPEnergyType vE0); ~StackInspector(); - void doStack(const TStack&); + void doStack(TStack const&); /** * To set a new E0, for example when a new shower event is started diff --git a/tests/framework/testProcessSequence.cpp b/tests/framework/testProcessSequence.cpp index efdaa2fc45fe55945b331d6dd741cfaf294dee09..e2dfbdbf07ec92427dc1c78f1be2e1320226b15d 100644 --- a/tests/framework/testProcessSequence.cpp +++ b/tests/framework/testProcessSequence.cpp @@ -26,6 +26,26 @@ using namespace std; static int const nData = 10; + +// 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}; +}; +// there is no real trajectory/track +struct DummyTrajectory {}; +// since there is no stack, there is also no view. This is a simplistic dummy object +// sufficient here. +struct DummyView { + DummyView(DummyData& p) + : p_(p) {} + DummyData& p_; + DummyData& parent() { return p_; } +}; + + + int globalCount = 0; // simple counter int checkDecay = 0; // use this as a bit field @@ -49,7 +69,7 @@ public: void setStep(LengthType const v) { step_ = v; } template <typename D, typename T> - ProcessReturn doContinuous(D& d, T&, bool const flag) const { + ProcessReturn doContinuous(D& d, T&, bool flag) const { flag_ = flag; CORSIKA_LOG_TRACE("ContinuousProcess1::DoContinuous"); checkCont |= 1; @@ -281,8 +301,7 @@ public: TimeType getLifetime(Particle&) const { return 2_s; } - template <typename TView> - void doDecay(TView&) const { + void doDecay(DummyView&) const { checkDecay |= 2; } }; @@ -292,9 +311,8 @@ public: Stack1(int const n) : StackProcess(n) {} template <typename TStack> - ProcessReturn doStack(TStack&) { + void doStack(TStack const&) { count_++; - return ProcessReturn::Ok; } int getCount() const { return count_; } @@ -302,22 +320,6 @@ private: int count_ = 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}; -}; -// there is no real trajectory/track -struct DummyTrajectory {}; -// since there is no stack, there is also no view. This is a simplistic dummy object -// sufficient here. -struct DummyView { - DummyView(DummyData& p) - : p_(p) {} - DummyData& p_; - DummyData& parent() { return p_; } -}; TEST_CASE("ProcessSequence General", "ProcessSequence") { @@ -727,34 +729,34 @@ TEST_CASE("ProcessSequence Indexing", "ProcessSequence") { SECTION("Indexing") { - int const n0 = count_continuous<Decay2>::count; - int const n1 = count_continuous<ContinuousProcess3>::count; - int const n2 = count_continuous<ContinuousProcess2, - count_continuous<ContinuousProcess3>::count>::count; + int const n0 = count_processes<Decay2>::count; + int const n1 = count_processes<ContinuousProcess3>::count; + int const n2 = count_processes<ContinuousProcess2, + count_processes<ContinuousProcess3>::count>::count; int const n1_b = - count_continuous<Process2, count_continuous<ContinuousProcess3>::count>::count; + count_processes<Process2, count_processes<ContinuousProcess3>::count>::count; int const n1_c = - count_continuous<ContinuousProcess3, count_continuous<Process2>::count>::count; + count_processes<ContinuousProcess3, count_processes<Process2>::count>::count; int const n12 = - count_continuous<ContinuousProcess2, - count_continuous<ContinuousProcess3, 10>::count>::count; + count_processes<ContinuousProcess2, + count_processes<ContinuousProcess3, 10>::count>::count; int const n11_b = - count_continuous<Process1, - count_continuous<ContinuousProcess3, 10>::count>::count; - int const n11_c = count_continuous<ContinuousProcess3, - count_continuous<Process1, 10>::count>::count; + count_processes<Process1, + count_processes<ContinuousProcess3, 10>::count>::count; + int const n11_c = count_processes<ContinuousProcess3, + count_processes<Process1, 10>::count>::count; - CHECK(n0 == 0); + CHECK(n0 == 1); CHECK(n1 == 1); - CHECK(n1_b == 1); - CHECK(n1_c == 1); + CHECK(n1_b == 2); + CHECK(n1_c == 2); CHECK(n2 == 2); - CHECK(n11_b == 11); - CHECK(n11_c == 11); + CHECK(n11_b == 12); + CHECK(n11_c == 12); CHECK(n12 == 12); - std::cout << count_continuous<ContinuousProcess3>::count << std::endl; - std::cout << count_continuous<Process3>::count << std::endl; + std::cout << count_processes<ContinuousProcess3>::count << std::endl; + std::cout << count_processes<Process3>::count << std::endl; struct SwitchSelect { SwitchResult operator()(DummyData const& p) const { @@ -775,15 +777,15 @@ TEST_CASE("ProcessSequence Indexing", "ProcessSequence") { auto sequence4 = make_sequence(ContinuousProcess1(0, 1_m), Process3(0), SwitchProcessSequence(sequence1, sequence2, select1)); - int const switch_seq_n = count_continuous<decltype(switch_seq)>::count; - int const sequence3_n = count_continuous<decltype(sequence3)>::count; + int const switch_seq_n = count_processes<decltype(switch_seq)>::count; + int const sequence3_n = count_processes<decltype(sequence3)>::count; CHECK(decltype(sequence1)::getNumberOfProcesses() == 3); - CHECK(count_continuous<decltype(sequence1)>::count == 3); - CHECK(count_continuous<decltype(sequence2)>::count == 4); + CHECK(count_processes<decltype(sequence1)>::count == 3); + CHECK(count_processes<decltype(sequence2)>::count == 4); CHECK(switch_seq_n == 7); CHECK(sequence3_n == 9); - CHECK(count_continuous<decltype(sequence4)>::count == 9); + CHECK(count_processes<decltype(sequence4)>::count == 9); std::cout << "switch_seq " << boost::typeindex::type_id<decltype(switch_seq)>().pretty_name()