From c157f6ac497a748e595bf8b29d401c36117aed9d Mon Sep 17 00:00:00 2001
From: Felix Riehn <friehn@lip.pt>
Date: Wed, 7 Aug 2024 11:07:28 +0000
Subject: [PATCH] Resolve "use epos internal decays for exotic resonances"

---
 applications/c8_air_shower.cpp                |  3 +-
 .../detail/modules/epos/InteractionModel.inl  | 46 ++++++++++++-
 corsika/modules/Epos.hpp                      |  6 +-
 corsika/modules/epos/InteractionModel.hpp     |  5 +-
 corsika/setup/SetupC7trackedParticles.hpp     | 25 +++++++
 tests/modules/testEpos.cpp                    | 68 ++++---------------
 6 files changed, 92 insertions(+), 61 deletions(-)
 create mode 100644 corsika/setup/SetupC7trackedParticles.hpp

diff --git a/applications/c8_air_shower.cpp b/applications/c8_air_shower.cpp
index 10a9f4867..40a762726 100644
--- a/applications/c8_air_shower.cpp
+++ b/applications/c8_air_shower.cpp
@@ -76,6 +76,7 @@
 
 #include <corsika/setup/SetupStack.hpp>
 #include <corsika/setup/SetupTrajectory.hpp>
+#include <corsika/setup/SetupC7trackedParticles.hpp>
 
 #include <boost/filesystem.hpp>
 
@@ -425,7 +426,7 @@ int main(int argc, char** argv) {
         std::make_shared<corsika::qgsjetII::Interaction>()};
   } else if (modelStr == "EPOS-LHC") {
     heModel = DynamicInteractionProcess<StackType>{
-        std::make_shared<corsika::epos::Interaction>()};
+        std::make_shared<corsika::epos::Interaction>(corsika::setup::C7trackedParticles)};
   } else {
     CORSIKA_LOG_CRITICAL("invalid choice \"{}\"; also check argument parser", modelStr);
     return EXIT_FAILURE;
diff --git a/corsika/detail/modules/epos/InteractionModel.inl b/corsika/detail/modules/epos/InteractionModel.inl
index 6d2b94c61..cf35f1aca 100644
--- a/corsika/detail/modules/epos/InteractionModel.inl
+++ b/corsika/detail/modules/epos/InteractionModel.inl
@@ -24,7 +24,8 @@
 
 namespace corsika::epos {
 
-  inline InteractionModel::InteractionModel(std::string const& dataPath,
+  inline InteractionModel::InteractionModel(std::set<Code> vList,
+                                            std::string const& dataPath,
                                             bool const epos_printout_on)
       : data_path_(dataPath)
       , epos_listing_(epos_printout_on) {
@@ -36,7 +37,45 @@ namespace corsika::epos {
         data_path_ = (std::string(corsika_data("EPOS").c_str()) + "/").c_str();
       }
       initialize();
+      if (vList.empty()) {
+        CORSIKA_LOGGER_DEBUG(logger_,
+                             "set all particles known to CORSIKA stable inside EPOS..");
+        setParticleListStable(get_all_particles());
+      } else {
+        CORSIKA_LOGGER_DEBUG(logger_, "set specific particles stable inside EPOS..");
+        setParticleListStable(vList);
+      }
+    }
+  }
+
+  inline void InteractionModel::setParticleListStable(std::set<Code> vPartList) const {
+    for (auto& p : vPartList) {
+      int const eid = convertToEposRaw(p);
+      if (eid != 0) {
+        // LCOV_EXCL_START
+        // this is only a safeguard against messing up the epos internals by initializing
+        // more than once.
+        unsigned int const n_particles_stable_epos =
+            ::epos::nodcy_.nrnody; // avoid waring -Wsign-compare
+        if (n_particles_stable_epos < ::epos::mxnody) {
+          CORSIKA_LOGGER_TRACE(logger_, "setting {} with EposId={} stable inside EPOS.",
+                               p, eid);
+          ::epos::nodcy_.nrnody = ::epos::nodcy_.nrnody + 1;
+          ::epos::nodcy_.nody[::epos::nodcy_.nrnody - 1] = eid;
+        } else {
+          CORSIKA_LOGGER_ERROR(logger_, "List of stable particles too long for Epos!");
+          throw std::runtime_error("Epos initialization error!");
+        }
+        // LCOV_EXCL_STOP
+      } else {
+        CORSIKA_LOG_TRACE(
+            "particle conversion Corsika-->Epos not known for {}. Using {}. Setting "
+            "unstable in Epos!",
+            p, eid);
+      }
     }
+    CORSIKA_LOGGER_DEBUG(logger_, "set {} particles stable inside Epos",
+                         ::epos::nodcy_.nrnody);
   }
 
   inline bool InteractionModel::isValid(Code const projectileId, Code const targetId,
@@ -103,7 +142,8 @@ namespace corsika::epos {
     ::epos::othe2_.iframe = 11; // cms frame
 
     // decay settings
-    ::epos::othe2_.idecay = 0; // no decays in epos
+    // activate decays in epos for particles defined by set_stable/set_unstable
+    // ::epos::othe2_.idecay = 0; // no decays in epos
 
     // set paths to tables in corsika data
     ::epos::datadir BASE(data_path_);
@@ -417,7 +457,7 @@ namespace corsika::epos {
     if (is_nucleus(targetId)) {
       targetA = get_nucleus_A(targetId);
       targetZ = get_nucleus_Z(targetId);
-      CORSIKA_LOGGER_DEBUG(logger_, "target: A={}, Z={} ", beamA, beamZ);
+      CORSIKA_LOGGER_DEBUG(logger_, "target: A={}, Z={} ", targetA, targetZ);
     }
     initializeEventCoM(projectileId, beamA, beamZ, targetId, targetA, targetZ, sqrtSNN);
 
diff --git a/corsika/modules/Epos.hpp b/corsika/modules/Epos.hpp
index 0fb3db624..9610edb38 100644
--- a/corsika/modules/Epos.hpp
+++ b/corsika/modules/Epos.hpp
@@ -26,5 +26,9 @@ namespace corsika::epos {
    * 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> {};
+  class Interaction : public InteractionModel, public InteractionProcess<Interaction> {
+  public:
+    Interaction(std::set<Code> const& stableList)
+        : InteractionModel{stableList} {};
+  };
 } // namespace corsika::epos
diff --git a/corsika/modules/epos/InteractionModel.hpp b/corsika/modules/epos/InteractionModel.hpp
index dcb841895..388298a4b 100644
--- a/corsika/modules/epos/InteractionModel.hpp
+++ b/corsika/modules/epos/InteractionModel.hpp
@@ -9,6 +9,7 @@
 #pragma once
 
 #include <tuple>
+#include <set>
 
 #include <corsika/framework/core/ParticleProperties.hpp>
 #include <corsika/framework/core/PhysicalUnits.hpp>
@@ -20,7 +21,7 @@ namespace corsika::epos {
   class InteractionModel {
 
   public:
-    InteractionModel(std::string const& dataPath = "",
+    InteractionModel(std::set<Code> = {}, std::string const& dataPath = "",
                      bool const epos_printout_on = false);
     ~InteractionModel();
 
@@ -101,7 +102,7 @@ namespace corsika::epos {
     // initialize and setParticlesStable are private since they can only be called once at
     // the beginning and are already called in the constructor!
     void initialize() const;
-    void setParticlesStable() const;
+    void setParticleListStable(std::set<Code>) const;
     inline static bool isInitialized_ = false;
 
     std::string data_path_;
diff --git a/corsika/setup/SetupC7trackedParticles.hpp b/corsika/setup/SetupC7trackedParticles.hpp
new file mode 100644
index 000000000..82c284431
--- /dev/null
+++ b/corsika/setup/SetupC7trackedParticles.hpp
@@ -0,0 +1,25 @@
+/*
+ * (c) Copyright 2024 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.
+ */
+
+namespace corsika::setup {
+
+  /**
+    \file SetupC7TrackedParticles.hpp
+
+    set of particles that should be tracked by corsika. all others should decay.
+    this is the corsika 7 configuration
+   */
+
+  std::set<Code> C7trackedParticles{
+      Code::Proton,        Code::Neutron,   Code::AntiProton,   Code::AntiNeutron,
+      Code::PiPlus,        Code::PiMinus,   Code::Pi0,          Code::KPlus,
+      Code::KMinus,        Code::K0Long,    Code::K0Short,      Code::Lambda0,
+      Code::Lambda0Bar,    Code::SigmaPlus, Code::SigmaPlusBar, Code::SigmaMinus,
+      Code::SigmaMinusBar, Code::Xi0,       Code::Xi0Bar,       Code::OmegaMinus,
+      Code::OmegaPlusBar,  Code::MuPlus,    Code::MuMinus};
+} // namespace corsika::setup
\ No newline at end of file
diff --git a/tests/modules/testEpos.cpp b/tests/modules/testEpos.cpp
index 4b984bbca..3235d8012 100644
--- a/tests/modules/testEpos.cpp
+++ b/tests/modules/testEpos.cpp
@@ -225,71 +225,31 @@ TEST_CASE("Epos", "modules") {
         {Oxygen::mass, {cs, 0_GeV, 0_GeV, 0_GeV}}));
   }
 
-  SECTION("InteractionInterface - nuclear projectile") {
+  SECTION("InteractionInterface - valid projectile target combinations") {
+
+    HEPMomentumType const P0 = 10_TeV;
+    Code const projectileId =
+        GENERATE(Code::Proton, Code::PiPlus, Code::KPlus, Code::Iron);
+    Code const targetId = GENERATE(Code::Proton, Code::Neutron, Code::Nitrogen);
 
-    HEPEnergyType const P0 = 10_TeV;
-    Code const pid = get_nucleus_code(40, 20);
     auto [stack, viewPtr] = setup::testing::setup_stack(
-        pid, P0, (DummyEnvironment::BaseNodeType* const)nodePtr, cs);
-    MomentumVector plab =
-        MomentumVector(cs, {P0, 0_eV, 0_eV}); // this is secret knowledge about
+        projectileId, P0, (DummyEnvironment::BaseNodeType* const)nodePtr, cs);
     test::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}});
+    model.doInteraction(
+        view, projectileId, targetId,
+        {calculate_total_energy(P0, get_mass(projectileId)), {cs, {P0, 0_eV, 0_eV}}},
+        {get_mass(targetId), {cs, 0_GeV, 0_GeV, 0_GeV}});
 
     //  simply check if stack is not empty after the event. Energy and momentum
     //  conservation will be tested elsewhere
     CHECK(view.getSize() > 0);
-
-    // 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(
-        pid, P0, (DummyEnvironment::BaseNodeType* const)nodePtr, cs);
-    MomentumVector plab =
-        MomentumVector(cs, {P0, 0_eV, 0_eV}); // this is secret knowledge about
-    test::StackView& view = *viewPtr;
+  SECTION("Decay config") {
+    logging::set_level(logging::level::debug);
 
-    // @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
+    InteractionModel model(std::set<Code>{Code::Proton, Code::PiPlus, Code::KPlus});
   }
 }
-- 
GitLab