From 7a6ae779ad04aa4022f22a8fc8e928b9bc5573da Mon Sep 17 00:00:00 2001
From: Felix Riehn <felix@matilda>
Date: Wed, 13 Jan 2021 18:47:51 +0000
Subject: [PATCH] added separate energy cut thresholds for electrons, photons,
 hadrons and muons

---
 corsika/detail/modules/ParticleCut.inl | 48 +++++++++++++++++++++++---
 corsika/modules/ParticleCut.hpp        | 30 ++++++++++++++--
 examples/cascade_example.cpp           |  2 +-
 examples/cascade_proton_example.cpp    |  2 +-
 examples/em_shower.cpp                 |  6 ++--
 examples/hybrid_MC.cpp                 |  2 +-
 examples/vertical_EAS.cpp              |  6 ++--
 tests/modules/testParticleCut.cpp      | 43 +++++++++++++++++++++--
 8 files changed, 120 insertions(+), 19 deletions(-)

diff --git a/corsika/detail/modules/ParticleCut.inl b/corsika/detail/modules/ParticleCut.inl
index 01d99891b..559102cc4 100644
--- a/corsika/detail/modules/ParticleCut.inl
+++ b/corsika/detail/modules/ParticleCut.inl
@@ -12,8 +12,40 @@
 
 namespace corsika {
 
+  ParticleCut::ParticleCut(const HEPEnergyType eEleCut, const HEPEnergyType ePhoCut,
+                           const HEPEnergyType eHadCut, const HEPEnergyType eMuCut,
+                           bool inv)
+      : electron_energy_cut_(eEleCut)
+      , photon_energy_cut_(ePhoCut)
+      , had_energy_cut_(eHadCut)
+      , mu_energy_cut_(eMuCut)
+      , doCutEm_(false)
+      , doCutInv_(inv)
+      , energy_(0_GeV)
+      , em_energy_(0_GeV)
+      , em_count_(0)
+      , inv_energy_(0_GeV)
+      , inv_count_(0) {}
+
+  ParticleCut::ParticleCut(const HEPEnergyType eHadCut, const HEPEnergyType eMuCut,
+                           bool inv)
+      : electron_energy_cut_(0_eV)
+      , photon_energy_cut_(0_eV)
+      , had_energy_cut_(eHadCut)
+      , mu_energy_cut_(eMuCut)
+      , doCutEm_(true)
+      , doCutInv_(inv)
+      , energy_(0_GeV)
+      , em_energy_(0_GeV)
+      , em_count_(0)
+      , inv_energy_(0_GeV)
+      , inv_count_(0) {}
+
   ParticleCut::ParticleCut(const HEPEnergyType eCut, bool em, bool inv)
-      : energy_cut_(eCut)
+      : electron_energy_cut_(eCut)
+      , photon_energy_cut_(eCut)
+      , had_energy_cut_(eCut)
+      , mu_energy_cut_(eCut)
       , doCutEm_(em)
       , doCutInv_(inv)
       , energy_(0_GeV)
@@ -25,13 +57,21 @@ namespace corsika {
   template <typename TParticle>
   bool ParticleCut::isBelowEnergyCut(TParticle const& vP) const {
     auto const energyLab = vP.getEnergy();
+    auto const pid = vP.getPID();
     // nuclei
-    if (vP.getPID() == Code::Nucleus) {
+    if (pid == Code::Nucleus) {
       // calculate energy per nucleon
       auto const ElabNuc = energyLab / vP.getNuclearA();
-      return (ElabNuc < energy_cut_);
+      return (ElabNuc < had_energy_cut_);
+    } else if (pid == Code::Gamma) {
+      return (energyLab < photon_energy_cut_);
+    } else if (pid == Code::Electron || pid == Code::Positron) {
+      return (energyLab < electron_energy_cut_);
+    } else if (is_muon(pid)) {
+      return (energyLab < mu_energy_cut_);
     } else {
-      return (energyLab < energy_cut_);
+      // assuming the rest are hadrons
+      return (energyLab < had_energy_cut_);
     }
   }
 
diff --git a/corsika/modules/ParticleCut.hpp b/corsika/modules/ParticleCut.hpp
index 098efc005..11d8508ad 100644
--- a/corsika/modules/ParticleCut.hpp
+++ b/corsika/modules/ParticleCut.hpp
@@ -17,11 +17,29 @@
 #include <corsika/setup/SetupTrajectory.hpp>
 
 namespace corsika {
-
+  /**
+     simple ParticleCut process. Goes through the secondaries of an interaction and
+   removes particles according to their energy. Particles with a time delay of more than
+   10ms are removed as well. Invisible particles (neutrinos) can be removed if selected.
+   **/
   class ParticleCut : public SecondariesProcess<ParticleCut>,
                       public ContinuousProcess<ParticleCut> {
 
   public:
+    /**
+     * particle cut with energy thresholds for electrons, photons,
+     *    hadrons (including nuclei with energy per nucleon) and muons
+     *    invisible particles (neutrinos) can be cut or not
+     **/
+    ParticleCut(const HEPEnergyType eEleCut, const HEPEnergyType ePhoCut,
+                const HEPEnergyType eHadCut, const HEPEnergyType eMuCut, bool inv);
+
+    //! simple cut. hadrons and muons are cut by threshold. EM particles are all
+    //! discarded.
+    ParticleCut(const HEPEnergyType eHadCut, const HEPEnergyType eMuCut, bool inv);
+
+    //! simplest cut. all particles have same threshold. EM particles can be set to be
+    //! discarded altogether.
     ParticleCut(const HEPEnergyType eCut, bool em, bool inv);
 
     void doSecondaries(corsika::setup::StackView&);
@@ -35,7 +53,10 @@ namespace corsika {
     void showResults();
     void reset();
 
-    HEPEnergyType getECut() const { return energy_cut_; }
+    HEPEnergyType getElectronECut() const { return electron_energy_cut_; }
+    HEPEnergyType getPhotonECut() const { return photon_energy_cut_; }
+    HEPEnergyType getMuonECut() const { return mu_energy_cut_; }
+    HEPEnergyType getHadronECut() const { return had_energy_cut_; }
     HEPEnergyType getInvEnergy() const { return inv_energy_; }
     HEPEnergyType getCutEnergy() const { return energy_; }
     HEPEnergyType getEmEnergy() const { return em_energy_; }
@@ -50,7 +71,10 @@ namespace corsika {
     bool isBelowEnergyCut(TParticle const&) const;
 
   private:
-    HEPEnergyType energy_cut_;
+    HEPEnergyType electron_energy_cut_;
+    HEPEnergyType photon_energy_cut_;
+    HEPEnergyType had_energy_cut_;
+    HEPEnergyType mu_energy_cut_;
     bool doCutEm_;
     bool doCutInv_;
     HEPEnergyType energy_ = 0 * electronvolt;
diff --git a/examples/cascade_example.cpp b/examples/cascade_example.cpp
index d14843c51..eea24768d 100644
--- a/examples/cascade_example.cpp
+++ b/examples/cascade_example.cpp
@@ -142,7 +142,7 @@ int main() {
   ParticleCut cut(80_GeV, true, true);
 
   TrackWriter trackWriter("tracks.dat");
-  BetheBlochPDG eLoss{showerAxis, cut.getECut()};
+  BetheBlochPDG eLoss{showerAxis, cut.getElectronECut()};
 
   // assemble all processes into an ordered process list
   auto sequence =
diff --git a/examples/cascade_proton_example.cpp b/examples/cascade_proton_example.cpp
index 003dc8558..e20864685 100644
--- a/examples/cascade_proton_example.cpp
+++ b/examples/cascade_proton_example.cpp
@@ -132,7 +132,7 @@ int main() {
 
   TrackWriter trackWriter("tracks.dat");
   ShowerAxis const showerAxis{injectionPos, Vector{rootCS, 0_m, 0_m, -100_km}, env};
-  BetheBlochPDG eLoss{showerAxis, cut.getECut()};
+  BetheBlochPDG eLoss{showerAxis, cut.getElectronECut()};
 
   // assemble all processes into an ordered process list
   // auto sequence = sibyll << decay << hadronicElastic << cut << trackWriter;
diff --git a/examples/em_shower.cpp b/examples/em_shower.cpp
index edb377953..d362ae14f 100644
--- a/examples/em_shower.cpp
+++ b/examples/em_shower.cpp
@@ -142,9 +142,9 @@ int main(int argc, char** argv) {
   // setup processes, decays and interactions
 
   // PROPOSAL processs proposal{...};
-  ParticleCut cut(10_GeV, false, true);
-  corsika::proposal::Interaction proposal(env, cut.getECut());
-  corsika::proposal::ContinuousProcess em_continuous(env, cut.getECut());
+  ParticleCut cut(10_GeV, 10_GeV, 100_PeV, 100_PeV, true);
+  corsika::proposal::Interaction proposal(env, cut.getElectronECut());
+  corsika::proposal::ContinuousProcess em_continuous(env, cut.getElectronECut());
   InteractionCounter proposalCounted(proposal);
 
   TrackWriter trackWriter("tracks.dat");
diff --git a/examples/hybrid_MC.cpp b/examples/hybrid_MC.cpp
index 71d8cf828..72e38e49d 100644
--- a/examples/hybrid_MC.cpp
+++ b/examples/hybrid_MC.cpp
@@ -207,7 +207,7 @@ int main(int argc, char** argv) {
   decaySibyll.printDecayConfig();
 
   ParticleCut cut{60_GeV, false, true};
-  BetheBlochPDG eLoss{showerAxis, cut.getECut()};
+  BetheBlochPDG eLoss{showerAxis, cut.getElectronECut()};
 
   CONEXhybrid conex_model(center, showerAxis, t, injectionHeight, E0,
                           get_PDG(Code::Proton));
diff --git a/examples/vertical_EAS.cpp b/examples/vertical_EAS.cpp
index 28488a1bd..46bd64522 100644
--- a/examples/vertical_EAS.cpp
+++ b/examples/vertical_EAS.cpp
@@ -221,9 +221,9 @@ int main(int argc, char** argv) {
 
   decaySibyll.printDecayConfig();
 
-  ParticleCut cut{60_GeV, false, true};
-  corsika::proposal::Interaction proposal(env, cut.getECut());
-  corsika::proposal::ContinuousProcess em_continuous(env, cut.getECut());
+  ParticleCut cut{60_GeV, 60_GeV, 60_GeV, 60_GeV, true};
+  corsika::proposal::Interaction proposal(env, cut.getElectronECut());
+  corsika::proposal::ContinuousProcess em_continuous(env, cut.getElectronECut());
   InteractionCounter proposalCounted(proposal);
 
   OnShellCheck reset_particle_mass(1.e-3, 1.e-1, false);
diff --git a/tests/modules/testParticleCut.cpp b/tests/modules/testParticleCut.cpp
index a88942301..1ac9beec5 100644
--- a/tests/modules/testParticleCut.cpp
+++ b/tests/modules/testParticleCut.cpp
@@ -24,7 +24,7 @@ using namespace corsika;
 
 TEST_CASE("ParticleCut", "[processes]") {
 
-  logging::set_level(logging::level::info);
+  logging::set_level(logging::level::debug);
   corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   feenableexcept(FE_INVALID);
@@ -51,7 +51,7 @@ TEST_CASE("ParticleCut", "[processes]") {
   SECTION("cut on particle type: inv") {
 
     ParticleCut cut(20_GeV, false, true);
-    CHECK(cut.getECut() == 20_GeV);
+    CHECK(cut.getHadronECut() == 20_GeV);
 
     // add primary particle to stack
     auto particle = stack.addParticle(std::make_tuple(
@@ -135,6 +135,43 @@ TEST_CASE("ParticleCut", "[processes]") {
     CHECK(view.getSize() == 13);
   }
 
+  SECTION("cut low energy: electrons, photons, hadrons and muons") {
+    ParticleCut cut(5_MeV,5_MeV,5_GeV,5_GeV, true);
+
+    // add primary particle to stack
+    auto particle = stack.addParticle(
+        std::make_tuple(Code::Proton, Eabove,
+                        MomentumVector(rootCS, {0_GeV, 0_GeV, 0_GeV}), point0, 0_ns));
+    // view on secondary particles
+    setup::StackView view(particle);
+    // ref. to primary particle through the secondary view.
+    // only this way the secondary view is populated
+    auto projectile = view.getProjectile();
+    // add secondaries 
+    projectile.addSecondary(std::make_tuple(Code::Gamma, 3_MeV,
+                                            MomentumVector(rootCS, {0_GeV, 0_GeV, 0_GeV}),
+                                            point0, 0_ns));
+    projectile.addSecondary(std::make_tuple(Code::Electron, 3_MeV,
+                                            MomentumVector(rootCS, {0_GeV, 0_GeV, 0_GeV}),
+                                            point0, 0_ns));
+    projectile.addSecondary(std::make_tuple(Code::PiPlus, 4_GeV,
+                                            MomentumVector(rootCS, {0_GeV, 0_GeV, 0_GeV}),
+                                            point0, 0_ns));
+    unsigned short A = 18;
+    unsigned short Z = 8;
+    projectile.addSecondary(std::make_tuple(Code::Nucleus, 4_GeV * A,
+                                            MomentumVector(rootCS, {0_GeV, 0_GeV, 0_GeV}),
+                                            point0, 0_ns, A, Z));
+    projectile.addSecondary(std::make_tuple(Code::Nucleus, 6_GeV * A,
+                                            MomentumVector(rootCS, {0_GeV, 0_GeV, 0_GeV}),
+                                            point0, 0_ns, A, Z));
+
+    cut.doSecondaries(view);
+
+    CHECK(view.getEntries() == 1);
+    CHECK(view.getSize() == 5);
+  }
+
   SECTION("cut on time") {
     ParticleCut cut(20_GeV, false, false);
     const TimeType too_late = 1_s;
@@ -170,7 +207,7 @@ TEST_CASE("ParticleCut", "[processes]") {
   SECTION("cut on DoContinous, just invisibles") {
 
     ParticleCut cut(20_GeV, false, true);
-    CHECK(cut.getECut() == 20_GeV);
+    CHECK(cut.getHadronECut() == 20_GeV);
 
     // add particles, all with energies above the threshold
     // only cut is by species
-- 
GitLab