From d05e531b68c9e6ffe048459dbb763149340196da Mon Sep 17 00:00:00 2001
From: Felix Riehn <felix@matilda>
Date: Sun, 9 Oct 2022 22:55:04 +0200
Subject: [PATCH] added Sophia as LE model to hadronicPhoton in proposal

---
 .../modules/proposal/HadronicPhotonModel.inl  | 106 ++++++++++--------
 .../modules/proposal/InteractionModel.inl     |  23 ++--
 .../modules/sophia/InteractionModel.inl       |   2 +-
 corsika/modules/PROPOSAL.hpp                  |  13 ++-
 .../modules/proposal/HadronicPhotonModel.hpp  |  16 +--
 corsika/modules/proposal/InteractionModel.hpp |  10 +-
 corsika/modules/sophia/InteractionModel.hpp   |   8 +-
 tests/modules/testProposal.cpp                |  25 +++--
 8 files changed, 119 insertions(+), 84 deletions(-)

diff --git a/corsika/detail/modules/proposal/HadronicPhotonModel.inl b/corsika/detail/modules/proposal/HadronicPhotonModel.inl
index 0eeb4afc6..1275fe35a 100644
--- a/corsika/detail/modules/proposal/HadronicPhotonModel.inl
+++ b/corsika/detail/modules/proposal/HadronicPhotonModel.inl
@@ -15,10 +15,12 @@
 
 namespace corsika::proposal {
 
-  template <typename THadronicModel>
-  inline HadronicPhotonModel<THadronicModel>::HadronicPhotonModel(
-      THadronicModel& _hadint, HEPEnergyType const& _heenthresholdNN)
-      : heHadronicInteraction_(_hadint)
+  template <typename THadronicLEModel, typename THadronicHEModel>
+  inline HadronicPhotonModel<THadronicLEModel, THadronicHEModel>::HadronicPhotonModel(
+      THadronicLEModel& _hadintLE, THadronicHEModel& _hadintHE,
+      HEPEnergyType const& _heenthresholdNN)
+      : leHadronicInteraction_(_hadintLE)
+      , heHadronicInteraction_(_hadintHE)
       , heHadronicModelThresholdLabNN_(_heenthresholdNN) {
     // check validity of threshold assuming photon-nucleon
     // sqrtS per target nucleon
@@ -27,8 +29,8 @@ namespace corsika::proposal {
     if (!heHadronicInteraction_.isValid(Code::Rho0, Code::Proton, sqrtS)) {
       CORSIKA_LOGGER_CRITICAL(
           logger_,
-          "Invalid energy threshold for hadron interaction model. theshold_lab= {} GeV, "
-          "theshold_com={} GeV",
+          "Invalid energy threshold for hadron interaction model. threshold_lab= {} GeV, "
+          "threshold_com={} GeV",
           _heenthresholdNN / 1_GeV, sqrtS / 1_GeV);
       throw std::runtime_error("Configuration error!");
     }
@@ -37,65 +39,77 @@ namespace corsika::proposal {
         _heenthresholdNN / 1_GeV);
   }
 
-  template <typename THadronicModel>
+  template <typename THadronicLEModel, typename THadronicHEModel>
   template <typename TStackView>
-  inline ProcessReturn HadronicPhotonModel<THadronicModel>::doHadronicPhotonInteraction(
+  inline ProcessReturn
+  HadronicPhotonModel<THadronicLEModel, THadronicHEModel>::doHadronicPhotonInteraction(
       TStackView& view, CoordinateSystemPtr const& labCS, FourMomentum const& photonP4,
       Code const& targetId) {
-    if (photonP4.getTimeLikeComponent() > heHadronicModelThresholdLabNN_) {
-      CORSIKA_LOGGER_TRACE(
-          logger_, "HE photo-hadronic interaction! calling hadronic interaction model..");
 
-      //  copy from sibyll::NuclearInteractionModel
-      //  temporarily add to stack, will be removed after interaction in DoInteraction
-      typename TStackView::inner_stack_value_type photonStack;
-      Point const pDummy(labCS, {0_m, 0_m, 0_m});
-      TimeType const tDummy = 0_ns;
-      Code const hadPhotonCode = Code::Rho0; // stand in for hadronic-photon
-      // target at rest
-      FourMomentum const targetP4(get_mass(targetId),
-                                  MomentumVector(labCS, {0_GeV, 0_GeV, 0_GeV}));
-      auto hadronicPhoton = photonStack.addParticle(std::make_tuple(
-          hadPhotonCode, photonP4.getTimeLikeComponent(),
-          photonP4.getSpaceLikeComponents().normalized(), pDummy, tDummy));
-      hadronicPhoton.setNode(view.getProjectile().getNode());
-      // create inelastic interaction of the hadronic photon
-      // create new StackView for the photon
-      TStackView photon_secondaries(hadronicPhoton);
+    //  temporarily add to stack, will be removed after interaction in DoInteraction
+    typename TStackView::inner_stack_value_type photonStack;
+    Point const pDummy(labCS, {0_m, 0_m, 0_m});
+    TimeType const tDummy = 0_ns;
+    // target at rest
+    FourMomentum const targetP4(get_mass(targetId),
+                                MomentumVector(labCS, {0_GeV, 0_GeV, 0_GeV}));
+    auto hadronicPhoton = photonStack.addParticle(
+        std::make_tuple(Code::Photon, photonP4.getTimeLikeComponent(),
+                        photonP4.getSpaceLikeComponents().normalized(), pDummy, tDummy));
+    hadronicPhoton.setNode(view.getProjectile().getNode());
+    // create inelastic interaction of the hadronic photon
+    // create new StackView for the photon
+    TStackView photon_secondaries(hadronicPhoton);
 
-      // call inner hadronic event generator
-      CORSIKA_LOGGER_TRACE(logger_, "{} + {} interaction. Ekinlab = {} GeV",
-                           hadPhotonCode, targetId,
-                           photonP4.getTimeLikeComponent() / 1_GeV);
-      // check if had. model can handle configuration
-      auto const sqrtSNN = (photonP4 + targetP4 / get_nucleus_A(targetId)).getNorm();
+    // call inner hadronic event generator
+    CORSIKA_LOGGER_TRACE(logger_, "{} + {} interaction. Ekinlab = {} GeV", Code::Photon,
+                         targetId, photonP4.getTimeLikeComponent() / 1_GeV);
+    // check if had. model can handle configuration
+    auto const sqrtSNN = (photonP4 + targetP4 / get_nucleus_A(targetId)).getNorm();
+    CORSIKA_LOGGER_DEBUG(logger_, "sqrtS={} GeV", sqrtSNN / 1_GeV);
+    if (photonP4.getTimeLikeComponent() > heHadronicModelThresholdLabNN_) {
+      CORSIKA_LOGGER_TRACE(logger_, "HE photo-hadronic interaction!");
       // when Sibyll is used for hadronic interactions Argon cannot be used as target
       // nucleus. Since PROPOSAL has a non-zero cross section for Argon
       // targets we have to check here if the model can handle Argon (see Issue #498)
-      if (!heHadronicInteraction_.isValid(hadPhotonCode, targetId, sqrtSNN)) {
+      if (!heHadronicInteraction_.isValid(Code::Rho0, targetId, sqrtSNN)) {
         CORSIKA_LOGGER_WARN(
             logger_,
             "HE interaction model cannot handle configuration in photo-hadronic "
             "interaction! projectile={}, target={} (A={}, Z={}), sqrt(S) per "
             "nuc.={:8.2f} "
             "GeV. Skipping secondary production!",
-            hadPhotonCode, targetId, get_nucleus_A(targetId), get_nucleus_Z(targetId),
+            Code::Rho0, targetId, get_nucleus_A(targetId), get_nucleus_Z(targetId),
             sqrtSNN / 1_GeV);
         return ProcessReturn::Ok;
       }
-      heHadronicInteraction_.doInteraction(photon_secondaries, hadPhotonCode, targetId,
+      heHadronicInteraction_.doInteraction(photon_secondaries, Code::Rho0, targetId,
                                            photonP4, targetP4);
-      for (const auto& pSec : photon_secondaries) {
-        auto const p3lab = pSec.getMomentum();
-        Code const pid = pSec.getPID();
-        HEPEnergyType const secEkin =
-            calculate_kinetic_energy(p3lab.getNorm(), get_mass(pid));
-        view.addSecondary(std::make_tuple(pid, secEkin, p3lab.normalized()));
-      }
     } else {
-      CORSIKA_LOGGER_TRACE(
-          logger_,
-          "LE photo-hadronic interaction! Production of secondaries not implemented..");
+      CORSIKA_LOGGER_TRACE(logger_,
+                           "LE photo-hadronic interaction! implemented via SOPHIA "
+                           "assuming a single nucleon as target");
+      // as LE model we only have SOPHIA which only handles photon-nucleon interactions
+      if (!leHadronicInteraction_.isValid(Code::Photon, Code::Proton, sqrtSNN)) {
+        CORSIKA_LOGGER_WARN(
+            logger_,
+            "LE interaction model cannot handle configuration in photo-hadronic "
+            "interaction! projectile={}, target={} (A={}, Z={}), sqrt(S) per "
+            "nuc.={:8.2f} "
+            "GeV. Skipping secondary production!",
+            Code::Photon, targetId, get_nucleus_A(targetId), get_nucleus_Z(targetId),
+            sqrtSNN / 1_GeV);
+        return ProcessReturn::Ok;
+      }
+      leHadronicInteraction_.doInteraction(photon_secondaries, Code::Photon, Code::Proton,
+                                           photonP4, targetP4 / get_nucleus_A(targetId));
+    }
+    for (const auto& pSec : photon_secondaries) {
+      auto const p3lab = pSec.getMomentum();
+      Code const pid = pSec.getPID();
+      HEPEnergyType const secEkin =
+          calculate_kinetic_energy(p3lab.getNorm(), get_mass(pid));
+      view.addSecondary(std::make_tuple(pid, secEkin, p3lab.normalized()));
     }
     return ProcessReturn::Ok;
   }
diff --git a/corsika/detail/modules/proposal/InteractionModel.inl b/corsika/detail/modules/proposal/InteractionModel.inl
index 8a0390e90..d8198bab5 100644
--- a/corsika/detail/modules/proposal/InteractionModel.inl
+++ b/corsika/detail/modules/proposal/InteractionModel.inl
@@ -18,16 +18,17 @@
 
 namespace corsika::proposal {
 
-  template <typename THadronicModel>
+  template <typename THadronicLEModel, typename THadronicHEModel>
   template <typename TEnvironment>
-  inline InteractionModel<THadronicModel>::InteractionModel(
-      TEnvironment const& _env, THadronicModel& _hadint,
+  inline InteractionModel<THadronicLEModel, THadronicHEModel>::InteractionModel(
+      TEnvironment const& _env, THadronicLEModel& _hadintLE, THadronicHEModel& _hadintHE,
       HEPEnergyType const& _enthreshold)
       : ProposalProcessBase(_env)
-      , HadronicPhotonModel<THadronicModel>(_hadint, _enthreshold) {}
+      , HadronicPhotonModel<THadronicLEModel, THadronicHEModel>(_hadintLE, _hadintHE,
+                                                                _enthreshold) {}
 
-  template <typename THadronicModel>
-  inline void InteractionModel<THadronicModel>::buildCalculator(
+  template <typename THadronicLEModel, typename THadronicHEModel>
+  inline void InteractionModel<THadronicLEModel, THadronicHEModel>::buildCalculator(
       Code code, NuclearComposition const& comp) {
     // search crosssection builder for given particle
     auto p_cross = cross.find(code);
@@ -52,9 +53,10 @@ namespace corsika::proposal {
         PROPOSAL::make_interaction(c, true, true));
   }
 
-  template <typename THadronicModel>
+  template <typename THadronicLEModel, typename THadronicHEModel>
   template <typename TStackView>
-  inline ProcessReturn InteractionModel<THadronicModel>::doInteraction(
+  inline ProcessReturn
+  InteractionModel<THadronicLEModel, THadronicHEModel>::doInteraction(
       TStackView& view, Code const projectileId, FourMomentum const& projectileP4) {
 
     auto const projectile = view.getProjectile();
@@ -138,9 +140,10 @@ namespace corsika::proposal {
     return ProcessReturn::Ok;
   }
 
-  template <typename THadronicModel>
+  template <typename THadronicLEModel, typename THadronicHEModel>
   template <typename TParticle>
-  inline CrossSectionType InteractionModel<THadronicModel>::getCrossSection(
+  inline CrossSectionType
+  InteractionModel<THadronicLEModel, THadronicHEModel>::getCrossSection(
       TParticle const& projectile, Code const projectileId,
       FourMomentum const& projectileP4) {
 
diff --git a/corsika/detail/modules/sophia/InteractionModel.inl b/corsika/detail/modules/sophia/InteractionModel.inl
index 89560a392..fee1146cb 100644
--- a/corsika/detail/modules/sophia/InteractionModel.inl
+++ b/corsika/detail/modules/sophia/InteractionModel.inl
@@ -80,7 +80,7 @@ namespace corsika::sophia {
     initial_(nucleonSophiaCode);
     double Enucleon = nucleonP4.getTimeLikeComponent() / 1_GeV;
     double Ephoton = photonP4.getTimeLikeComponent() / 1_GeV;
-    double theta = 0.0; // set head on collision
+    double theta = 0.0; // set nucleon at rest in collision
     int Imode;
     CORSIKA_LOGGER_DEBUG(logger_,
                         "calling SOPHIA eventgen with L0={}, E0={}, eps={},theta={}",
diff --git a/corsika/modules/PROPOSAL.hpp b/corsika/modules/PROPOSAL.hpp
index 13e18c3de..81066911f 100644
--- a/corsika/modules/PROPOSAL.hpp
+++ b/corsika/modules/PROPOSAL.hpp
@@ -13,12 +13,15 @@
 
 namespace corsika::proposal {
 
-  template <typename THadronicModel>
-  class Interaction : public InteractionModel<THadronicModel>,
-                      public InteractionProcess<Interaction<THadronicModel>> {
+  template <typename THadronicLEModel, typename THadronicHEModel>
+  class Interaction
+      : public InteractionModel<THadronicLEModel, THadronicHEModel>,
+        public InteractionProcess<Interaction<THadronicLEModel, THadronicHEModel>> {
   public:
     template <typename TEnvironment>
-    Interaction(TEnvironment const& env, THadronicModel& model, HEPEnergyType const& thr)
-        : InteractionModel<THadronicModel>(env, model, thr) {}
+    Interaction(TEnvironment const& env, THadronicLEModel& modelLE,
+                THadronicHEModel& modelHE, HEPEnergyType const& thr)
+        : InteractionModel<THadronicLEModel, THadronicHEModel>(env, modelLE, modelHE,
+                                                               thr) {}
   };
 } // namespace corsika::proposal
diff --git a/corsika/modules/proposal/HadronicPhotonModel.hpp b/corsika/modules/proposal/HadronicPhotonModel.hpp
index 6fe6b88bb..602d3caf5 100644
--- a/corsika/modules/proposal/HadronicPhotonModel.hpp
+++ b/corsika/modules/proposal/HadronicPhotonModel.hpp
@@ -15,17 +15,18 @@
 namespace corsika::proposal {
 
   //! Implements the production of secondary hadrons for the hadronic interaction of real
-  //! and virtual photons. At high energies an external model
-  //! is needed that implements the doInteraction(TSecondaries& view, Code const
-  //! projectile, Code const target,FourMomentum const& projectileP4, FourMomentum const&
-  //! targetP4) routine. Low energy interactions are currently not implemented. The
+  //! and virtual photons for PROPOSAL. The model distinguishes between resonance
+  //! production (LE) and continuum (HE). HE production replaces the photon with a rho0.
+  //! External models are needed that implement the hadronic particle production via the
+  //! doInteraction(TSecondaries& view, Code const projectile, Code const target,
+  //! FourMomentum const& projectileP4, FourMomentum const& targetP4) routine. The
   //! threshold between LE and HE interactions is defined in lab energy.
   //! @tparam THadronicModel
 
-  template <class THadronicModel>
+  template <class THadronicLEModel, class THadronicHEModel>
   class HadronicPhotonModel {
   public:
-    HadronicPhotonModel(THadronicModel&, HEPEnergyType const&);
+    HadronicPhotonModel(THadronicLEModel&, THadronicHEModel&, HEPEnergyType const&);
     //!
     //! Calculate produce the hadronic secondaries in a hadronic photon interaction and
     //! store them on the particle stack.
@@ -36,7 +37,8 @@ namespace corsika::proposal {
 
   private:
     inline static auto logger_{get_logger("corsika_proposal_HadronicPhotonModel")};
-    THadronicModel& heHadronicInteraction_;
+    THadronicLEModel& leHadronicInteraction_;
+    THadronicHEModel& heHadronicInteraction_;
     //! threshold for high energy hadronic interaction model. Lab. energy per nucleon
     HEPEnergyType const heHadronicModelThresholdLabNN_;
   };
diff --git a/corsika/modules/proposal/InteractionModel.hpp b/corsika/modules/proposal/InteractionModel.hpp
index f25815322..ae9d3b7e7 100644
--- a/corsika/modules/proposal/InteractionModel.hpp
+++ b/corsika/modules/proposal/InteractionModel.hpp
@@ -33,9 +33,10 @@ namespace corsika::proposal {
   //! @tparam THadronicModel
   //!
 
-  template <class THadronicModel>
-  class InteractionModel : public ProposalProcessBase,
-                           public HadronicPhotonModel<THadronicModel> {
+  template <class THadronicLEModel, class THadronicHEModel>
+  class InteractionModel
+      : public ProposalProcessBase,
+        public HadronicPhotonModel<THadronicLEModel, THadronicHEModel> {
 
     enum { eSECONDARIES, eINTERACTION };
     using calculator_t = std::tuple<std::unique_ptr<PROPOSAL::SecondariesCalculator>,
@@ -57,7 +58,8 @@ namespace corsika::proposal {
     //! compositions and stochastic description limited by the particle cut.
     //!
     template <typename TEnvironment>
-    InteractionModel(TEnvironment const& env, THadronicModel&, HEPEnergyType const&);
+    InteractionModel(TEnvironment const& env, THadronicLEModel&, THadronicHEModel&,
+                     HEPEnergyType const&);
 
     //!
     //! Calculate the rates for the different targets and interactions. Sample a
diff --git a/corsika/modules/sophia/InteractionModel.hpp b/corsika/modules/sophia/InteractionModel.hpp
index e2f6d7636..f6e6d8fd7 100644
--- a/corsika/modules/sophia/InteractionModel.hpp
+++ b/corsika/modules/sophia/InteractionModel.hpp
@@ -62,9 +62,11 @@ namespace corsika::sophia {
      * @return inelastic cross section
      * elastic cross section
      */
-    CrossSectionType getCrossSection(Code const projectile, Code const target,
-                                     FourMomentum const& projectileP4,
-                                     FourMomentum const& targetP4) const {
+    CrossSectionType getCrossSection(
+        [[maybe_unused]] Code const projectile, [[maybe_unused]] Code const target,
+        [[maybe_unused]] FourMomentum const& projectileP4,
+        [[maybe_unused]] FourMomentum const& targetP4) const {
+      CORSIKA_LOGGER_ERROR(logger_, "cross section not implemented in SOPHIA!");
       return CrossSectionType::zero();
     }
     /**
diff --git a/tests/modules/testProposal.cpp b/tests/modules/testProposal.cpp
index eb186c358..299c913fe 100644
--- a/tests/modules/testProposal.cpp
+++ b/tests/modules/testProposal.cpp
@@ -12,6 +12,7 @@
 #include <SetupTestStack.hpp>
 #include <catch2/catch.hpp>
 #include <tuple>
+#include "corsika/framework/core/PhysicalUnits.hpp"
 
 using namespace corsika;
 using namespace corsika::proposal;
@@ -26,7 +27,8 @@ using DummyEnvironment = Environment<DummyEnvironmentInterface>;
 
 class DummyHadronicModel {
 public:
-  DummyHadronicModel(){};
+  DummyHadronicModel(HEPEnergyType thr)
+      : threshold_(thr){};
 
   template <typename TSecondaryView>
   void doInteraction(TSecondaryView& view, Code const, Code const,
@@ -42,8 +44,11 @@ public:
     }
   }
   bool constexpr isValid(Code const, Code const, HEPEnergyType const sqrsNN) const {
-    return (sqrsNN >= 10_GeV);
+    return (sqrsNN >= threshold_);
   };
+
+private:
+  HEPEnergyType threshold_;
 };
 
 TEST_CASE("ProposalInterface", "modules") {
@@ -61,14 +66,18 @@ TEST_CASE("ProposalInterface", "modules") {
   RNGManager<>::getInstance().registerRandomStream("proposal");
 
   SECTION("InteractionInterface - hadronic photon model threshold") {
-    DummyHadronicModel hadModel;
-    HEPEnergyType heThresholdLab1 = 10_GeV;
-    CHECK_THROWS(corsika::proposal::InteractionModel(*env, hadModel, heThresholdLab1));
+    DummyHadronicModel hadModelLE(100_MeV);
+    DummyHadronicModel hadModelHE(10_GeV);
+    HEPEnergyType heThresholdLab1 = 12_GeV;
+    CHECK_THROWS(corsika::proposal::InteractionModel(*env, hadModelLE, hadModelHE,
+                                                     heThresholdLab1));
   }
 
-  DummyHadronicModel hadModel;
+  DummyHadronicModel hadModelLE(100_MeV);
+  DummyHadronicModel hadModelHE(10_GeV);
   HEPEnergyType heThresholdLab = 80_GeV;
-  corsika::proposal::InteractionModel emModel(*env, hadModel, heThresholdLab);
+  corsika::proposal::InteractionModel emModel(*env, hadModelLE, hadModelHE,
+                                              heThresholdLab);
 
   SECTION("InteractionInterface - cross section") {
     auto& stack = *stackPtr;
@@ -92,7 +101,7 @@ TEST_CASE("ProposalInterface", "modules") {
     CHECK(emModel.doHadronicPhotonInteraction(view, cs, P4, Code::Oxygen) ==
           ProcessReturn::Ok);
     // no LE interactions
-    CHECK(stack.getEntries() == 1);
+    CHECK(stack.getEntries() == 6);
     CORSIKA_LOG_INFO("Number of particles produced in hadronic photon interaction: {}",
                      stack.getEntries() - 1);
   }
-- 
GitLab