diff --git a/corsika/framework/process/DynamicInteractionProcess.hpp b/corsika/framework/process/DynamicInteractionProcess.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6cf76684d3c30f8c5d953c0e42b10959c29c07be --- /dev/null +++ b/corsika/framework/process/DynamicInteractionProcess.hpp @@ -0,0 +1,111 @@ +/* + * (c) Copyright 2023 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/geometry/FourVector.hpp> +#include <corsika/framework/core/Logging.hpp> +#include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/framework/process/InteractionProcess.hpp> +#include <corsika/framework/process/ProcessTraits.hpp> + +#include <boost/type_index.hpp> + +#include <memory> + +namespace corsika { + + /** + * This class allows selecting/using different InteractionProcesses at runtime without + * recompiling the process sequence. The implementation is based on the "type-erasure" + * technique. + * + * @tparam TStack the stack type; has to match the one used by Cascade together with + * the ProcessSequence containing the DynamicInteractionProcess. + */ + template <typename TStack> + class DynamicInteractionProcess + : InteractionProcess<DynamicInteractionProcess<TStack>> { + public: + using stack_view_type = typename TStack::stack_view_type; + + DynamicInteractionProcess() = default; + + /** + * Create new DynamicInteractionProcess. Calls to this instance will be forwared + * to the process referred to by obj. The newly created DynamicInteractionProcess + * shares ownership of the underlying process, so that you do not need to worry + * about it going out of scope. + */ + template <typename TInteractionProcess> + DynamicInteractionProcess(std::shared_ptr<TInteractionProcess> obj) + : concept_{std::make_unique<ConcreteModel<TInteractionProcess>>(std::move(obj))} { + CORSIKA_LOG_DEBUG("creating DynamicInteractionProcess from {}", + boost::typeindex::type_id<TInteractionProcess>().pretty_name()); + + // poor man's check while we don't have C++20 concepts + static_assert(is_interaction_process_v<TInteractionProcess>); + + // since we only forward interaction process API, all other types of corsika + // processes are prohibited + static_assert(!is_continuous_process_v<TInteractionProcess>); + static_assert(!is_decay_process_v<TInteractionProcess>); + static_assert(!is_stack_process_v<TInteractionProcess>); + static_assert(!is_cascade_equations_process_v<TInteractionProcess>); + static_assert(!is_secondaries_process_v<TInteractionProcess>); + static_assert(!is_boundary_process_v<TInteractionProcess>); + static_assert(!is_cascade_equations_process_v<TInteractionProcess>); + } + + /// forwards arguments to doInteraction() of wrapped instance + void doInteraction(stack_view_type& view, Code projectileId, Code targetId, + FourMomentum const& projectileP4, FourMomentum const& targetP4) { + return concept_->doInteraction(view, projectileId, targetId, projectileP4, + targetP4); + } + + /// forwards arguments to getCrossSection() of wrapped instance + CrossSectionType getCrossSection(Code projectileId, Code targetId, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) const { + return concept_->getCrossSection(projectileId, targetId, projectileP4, targetP4); + } + + private: + struct IInteractionModel { + virtual ~IInteractionModel() = default; + virtual void doInteraction(stack_view_type&, Code, Code, FourMomentum const&, + FourMomentum const&) = 0; + virtual CrossSectionType getCrossSection(Code, Code, FourMomentum const&, + FourMomentum const&) const = 0; + }; + + template <typename TModel> + struct ConcreteModel final : IInteractionModel { + ConcreteModel(std::shared_ptr<TModel> obj) noexcept + : model_{std::move(obj)} {} + + void doInteraction(stack_view_type& view, Code projectileId, Code targetId, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) override { + return model_->doInteraction(view, projectileId, targetId, projectileP4, + targetP4); + } + + CrossSectionType getCrossSection(Code projectileId, Code targetId, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) const override { + return model_->getCrossSection(projectileId, targetId, projectileP4, targetP4); + } + + private: + std::shared_ptr<TModel> model_; + }; + + std::unique_ptr<IInteractionModel> concept_; + }; +} // namespace corsika diff --git a/examples/corsika.cpp b/examples/corsika.cpp index a7e9dca6b2666b4dcda9176eaadba0575af135f1..48bedadae945365a92cbab82f69af5055de500bb 100644 --- a/examples/corsika.cpp +++ b/examples/corsika.cpp @@ -19,6 +19,7 @@ #include <corsika/framework/geometry/PhysicalGeometry.hpp> #include <corsika/framework/geometry/Plane.hpp> #include <corsika/framework/geometry/Sphere.hpp> +#include <corsika/framework/process/DynamicInteractionProcess.hpp> #include <corsika/framework/process/InteractionCounter.hpp> #include <corsika/framework/process/ProcessSequence.hpp> #include <corsika/framework/process/SwitchProcessSequence.hpp> @@ -65,8 +66,10 @@ #include <CLI/Config.hpp> #include <CLI/Formatter.hpp> +#include <cstdlib> #include <iomanip> #include <limits> +#include <string> #include <string_view> /* @@ -165,6 +168,10 @@ int main(int argc, char** argv) { ->default_val("info") ->check(CLI::IsMember({"warn", "info", "debug", "trace"})) ->group("Misc."); + app.add_option("-M,--hadronModel", "High-energy hadronic interaction model") + ->default_val("SIBYLL-2.3d") + ->check(CLI::IsMember({"SIBYLL-2.3d", "QGSJet-II.04", "EPOS-LHC"})) + ->group("Misc."); // parse the command line options into the variables CLI11_PARSE(app, argc, argv); @@ -287,8 +294,26 @@ int main(int argc, char** argv) { TrackWriter tracks; output.add("tracks", tracks); - corsika::sibyll::Interaction sibyll{env}; - InteractionCounter sibyllCounted{sibyll}; + DynamicInteractionProcess<setup::Stack<EnvType>> heModel; + + // have SIBYLL always for PROPOSAL photo-hadronic interactions + auto sibyll = std::make_shared<corsika::sibyll::Interaction>(env); + + if (auto const modelStr = app["--hadronModel"]->as<std::string_view>(); + modelStr == "SIBYLL-2.3d") { + heModel = DynamicInteractionProcess<setup::Stack<EnvType>>{sibyll}; + } else if (modelStr == "QGSJet-II.04") { + heModel = DynamicInteractionProcess<setup::Stack<EnvType>>{ + std::make_shared<corsika::qgsjetII::Interaction>()}; + } else if (modelStr == "EPOS-LHC") { + heModel = DynamicInteractionProcess<setup::Stack<EnvType>>{ + std::make_shared<corsika::epos::Interaction>()}; + } else { + CORSIKA_LOG_CRITICAL("invalid choice \"{}\"; also check argument parser", modelStr); + return EXIT_FAILURE; + } + + InteractionCounter heCounted{heModel}; corsika::pythia8::Decay decayPythia; @@ -327,7 +352,7 @@ int main(int argc, char** argv) { HEPEnergyType heHadronModelThreshold = 63.1_GeV; corsika::proposal::Interaction emCascade( - env, sophia, sibyll.getHadronInteractionModel(), heHadronModelThreshold); + env, sophia, sibyll->getHadronInteractionModel(), heHadronModelThreshold); // use BetheBlochPDG for hadronic continuous losses, and proposal otherwise corsika::proposal::ContinuousProcess<SubWriter<decltype(dEdX)>> emContinuousProposal( @@ -356,11 +381,9 @@ int main(int argc, char** argv) { bool operator()(const Particle& p) const { return (p.getKineticEnergy() < cutE_); } }; auto hadronSequence = - make_select(EnergySwitch(heHadronModelThreshold), urqmdCounted, sibyllCounted); + make_select(EnergySwitch(heHadronModelThreshold), urqmdCounted, heCounted); auto decaySequence = make_sequence(decayPythia, decaySibyll); - TrackWriter trackWriter{tracks}; - // observation plane Plane const obsPlane(showerCore, DirectionVector(rootCS, {0., 0., 1.})); ObservationPlane<setup::Tracking, ParticleWriterParquet> observationLevel{ @@ -430,9 +453,7 @@ int main(int argc, char** argv) { Efinal / 1_GeV, dEdX.getEnergyLost() / 1_GeV, observationLevel.getEnergyGround() / 1_GeV, (Efinal / E0 - 1) * 100); - // auto const hists = heModelCounted.getHistogram() + - // urqmdCounted.getHistogram(); - auto const hists = sibyllCounted.getHistogram() + urqmdCounted.getHistogram(); + auto const hists = heCounted.getHistogram() + urqmdCounted.getHistogram(); save_hist(hists.labHist(), labHist_file, true); save_hist(hists.CMSHist(), cMSHist_file, true); @@ -440,4 +461,6 @@ int main(int argc, char** argv) { // and finalize the output on disk output.endOfLibrary(); + + return EXIT_SUCCESS; } diff --git a/tests/framework/CMakeLists.txt b/tests/framework/CMakeLists.txt index caaec9e4cc04763a30ceec719107e57f9b91a578..14670134dba78d4c20cfae3c4d4930bc3eeeb431 100644 --- a/tests/framework/CMakeLists.txt +++ b/tests/framework/CMakeLists.txt @@ -4,6 +4,7 @@ set (test_framework_sources testFourVector.cpp testSaveBoostHistogram.cpp testClassTimer.cpp + testDynamicInteractionProcess.cpp; testLogging.cpp testParticles.cpp testStackInterface.cpp diff --git a/tests/framework/testDynamicInteractionProcess.cpp b/tests/framework/testDynamicInteractionProcess.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7c68e341babab575ce04bcf8975c09e455d5ef8f --- /dev/null +++ b/tests/framework/testDynamicInteractionProcess.cpp @@ -0,0 +1,143 @@ +/* + * (c) Copyright 2022 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/DynamicInteractionProcess.hpp> +#include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/framework/geometry/FourVector.hpp> +#include <corsika/framework/geometry/CoordinateSystem.hpp> + +#include <catch2/catch.hpp> + +using namespace corsika; + +struct DummyStack { + using stack_view_type = int&; +}; + +struct DummyProcessA : public InteractionProcess<DummyProcessA> { + int id_; + + DummyProcessA(int id) + : id_{id} {} + + // mockup to "identify" the process via its ID + CrossSectionType getCrossSection(Code, Code, FourMomentum const&, + FourMomentum const&) const { + return id_ * 10_mb; + } + + // mockup: set argument to our ID + template <typename TArgument> + void doInteraction(TArgument& argument, Code, Code, FourMomentum const&, + FourMomentum const&) { + argument = id_ * 10; + } + + // to make sure we can test proper handling of dangling references and + // stuff like that + ~DummyProcessA() { id_ = 0; } + + // prevent copying + DummyProcessA(DummyProcessA const&) = delete; + DummyProcessA& operator=(DummyProcessA const&) = delete; + + // allow moving + DummyProcessA(DummyProcessA&&) = default; + DummyProcessA& operator=(DummyProcessA&&) = default; +}; + +// same as above to have another class +struct DummyProcessB : public InteractionProcess<DummyProcessB> { + int id_; + + DummyProcessB(int id) + : id_{id} {} + + CrossSectionType getCrossSection(Code, Code, FourMomentum const&, + FourMomentum const&) const { + return id_ * 1_mb; + } + + template <typename TArgument> + void doInteraction(TArgument& argument, Code, Code, FourMomentum const&, + FourMomentum const&) { + argument = id_; + } + + // to make sure we can test proper handling of dangling references and + // stuff like that + ~DummyProcessB() { id_ = 0; } + + // prevent copying + DummyProcessB(DummyProcessB const&) = delete; + DummyProcessB& operator=(DummyProcessB const&) = delete; + + // allow moving + DummyProcessB(DummyProcessB&&) = default; + DummyProcessB& operator=(DummyProcessB&&) = default; +}; + +TEST_CASE("DynamicInteractionProcess", "[process]") { + auto const rootCS = get_root_CoordinateSystem(); + FourMomentum const fourVec{1_GeV, {rootCS, 1_GeV, 1_GeV, 1_GeV}}; + + std::shared_ptr<DummyProcessA> a = std::make_shared<DummyProcessA>(1); + std::shared_ptr<DummyProcessB> b = std::make_shared<DummyProcessB>(2); + + SECTION("getCrossSection") { + DynamicInteractionProcess<DummyStack> dynamic; + + dynamic = a; + REQUIRE(dynamic.getCrossSection(Code::Electron, Code::Electron, fourVec, fourVec) / + 10_mb == + Approx(1).epsilon(1e-6)); + + dynamic = b; + REQUIRE(dynamic.getCrossSection(Code::Electron, Code::Electron, fourVec, fourVec) / + 2_mb == + Approx(1).epsilon(1e-6)); + + dynamic = DynamicInteractionProcess<DummyStack>{std::make_shared<DummyProcessA>(3)}; + REQUIRE(dynamic.getCrossSection(Code::Electron, Code::Electron, fourVec, fourVec) / + 30_mb == + Approx(1).epsilon(1e-6)); + + dynamic = std::make_shared<DummyProcessB>(4); + REQUIRE(dynamic.getCrossSection(Code::Electron, Code::Electron, fourVec, fourVec) / + 4_mb == + Approx(1).epsilon(1e-6)); + + // test process going out of scope + { dynamic = std::make_shared<DummyProcessB>(5); } + REQUIRE(dynamic.getCrossSection(Code::Electron, Code::Electron, fourVec, fourVec) / + 5_mb == + Approx(1).epsilon(1e-6)); + } + + SECTION("doInteraction") { + int value{}; + + DynamicInteractionProcess<DummyStack> dynamic; + + dynamic = a; + dynamic.doInteraction(value, Code::Electron, Code::Electron, fourVec, fourVec); + REQUIRE(value == 10); + + dynamic = b; + dynamic.doInteraction(value, Code::Electron, Code::Electron, fourVec, fourVec); + REQUIRE(value == 2); + + dynamic = DynamicInteractionProcess<DummyStack>{std::make_shared<DummyProcessA>(3)}; + dynamic.doInteraction(value, Code::Electron, Code::Electron, fourVec, fourVec); + REQUIRE(value == 30); + + dynamic = std::make_shared<DummyProcessB>(4); + dynamic.doInteraction(value, Code::Electron, Code::Electron, fourVec, fourVec); + REQUIRE(value == 4); + } +}