From dd14940d325604f6122519074e6212546f64f57c Mon Sep 17 00:00:00 2001
From: Maximilian Reininghaus <maximilian.reininghaus@kit.edu>
Date: Thu, 2 Feb 2023 14:56:24 +0100
Subject: [PATCH] first steps

---
 .../detail/modules/fluka/InteractionModel.inl | 157 ++++++++++++++++++
 corsika/modules/fluka/InteractionModel.hpp    |  46 +++++
 src/modules/fluka/CMakeLists.txt              |  26 +++
 src/modules/fluka/code_generator.py           | 102 ++++++++++++
 src/modules/fluka/fluka_codes.dat             | 123 ++++++++++++++
 tests/modules/testFluka.cpp                   |  38 +++++
 6 files changed, 492 insertions(+)
 create mode 100644 corsika/detail/modules/fluka/InteractionModel.inl
 create mode 100644 corsika/modules/fluka/InteractionModel.hpp
 create mode 100644 src/modules/fluka/CMakeLists.txt
 create mode 100755 src/modules/fluka/code_generator.py
 create mode 100644 src/modules/fluka/fluka_codes.dat
 create mode 100644 tests/modules/testFluka.cpp

diff --git a/corsika/detail/modules/fluka/InteractionModel.inl b/corsika/detail/modules/fluka/InteractionModel.inl
new file mode 100644
index 000000000..f7ae6fd32
--- /dev/null
+++ b/corsika/detail/modules/fluka/InteractionModel.inl
@@ -0,0 +1,157 @@
+/*
+ * (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.
+ */
+ 
+#pragma once
+
+#include <algorithm>
+#include <vector>
+#include <iterator>
+#include <set>
+#include <utility>
+
+#include <boost/iterator/zip_iterator.hpp>
+
+#include <corsika/media/Environment.hpp>
+#include <corsika/media/NuclearComposition.hpp>
+#include <corsika/framework/geometry/FourVector.hpp>
+
+#include <corsika/framework/core/PhysicalUnits.hpp>
+
+#include <FLUKA.hpp>
+
+namespace corsika::fluka {
+  template <typename TEnvironment>
+  inline InteractionModel::InteractionModel(TEnvironment const& env) :materials_{genFlukaMaterials(env)} {
+      for (auto const& [code, matno]: materials_) {
+          std::cout << code << "    " << matno << std::endl;
+      }
+    }
+    
+  inline bool InteractionModel::isValid(Code projectileID, Code targetID /*, HEPEnergyType sqrtS*/) {
+      static std::array<Code> constexpr validProjectiles{Code::Proton, Code::AntiProton, Code::Neutron, Code::AntiNeutron,
+          Code::PiPlus, Code::PiMinus, Code::KPlus, Code::KMinus, Code::K0Long, Code::K0Short}
+          
+      if (std::find(validProjectiles.cbegin(), validProjectiles.cend(), projectileID) == validProjectiles.cend()) {
+          // invalid projectile
+        return false;
+      }
+      
+      if (getMaterialIndex(targetID) == -1) {
+        // unknown material
+        return false;
+      }
+        
+      // TODO: check validity range of sqrtS
+  }
+  
+  inline int InteractionModel::getMaterialIndex(Code targetID) const {
+    if (auto it = std::find(materials_.cbegin(), materials_.cend(), [=targetID](std::pair<Code, int>& p) {return p.first == targetID;});
+        it == materials_.cend()) {
+        return -1;
+    } else {
+        return it->second;
+    }
+  }
+        
+  inline CrossSectionType InteractionModel::getCrossSection(
+      Code const projectileId, Code const targetId, FourMomentum const& projectileP4,
+      FourMomentum const& targetP4) const {
+          
+    if (!isValid(projectileId, targetId)) { return CrossSectionType::zero(); }
+    
+    COMBoost const targetRestBoost{projecileP4.getSpaceLikeComponents(), get_mass(targetId)};
+    FourMomentum const projectileLab4mom = targetRestBoost.toCOM(projectileP4);
+    projec
+    
+    int const iflxyz = 1;
+    
+    // SIGREA = SGMXYZ ( KPROJ, MMAT, EKIN, PPROJ, iflxyz );
+  }
+      
+      template <typename TSecondaryView>
+      inline void InteractionModel::doInteraction(
+      TSecondaryView& view, Code const projectileId, Code const targetId,
+      FourMomentum const& projectileP4, FourMomentum const& targetP4) {
+          
+      }
+  
+  
+  template <typename TEnvironment>
+  inline std::vector<std::pair<Code, int>> InteractionModel::genFlukaMaterials(TEnvironment const& env) {
+      auto const& universe = *(env.getUniverse());
+    // generate complete list of all nuclei types in universe
+
+    auto const allElementsInUniverse = std::invoke([&]() {
+      std::set<Code> allElementsInUniverse;
+      auto collectElements = [&](auto& vtn) {
+        if (vtn.hasModelProperties()) {
+          auto const& comp =
+              vtn.getModelProperties().getNuclearComposition().getComponents();
+          for (auto const c : comp) allElementsInUniverse.insert(c);
+        }
+      };
+      universe.walk(collectElements);
+      return allElementsInUniverse;
+    });
+    
+    /* 
+     * We define one material per element/isotope we have in C8. Cross-section averaging and
+     * target isotope selection happen in C8.
+     */
+    
+    int const nElements = allElementsInUniverse.size();
+    auto nelmfl = std::make_unique<int[]>(nElements);
+    std::vector<int> izelfl;
+    izelfl.reserve(nElements);
+    auto wfelml = std::make_unique<double[]>(nElements);
+    auto const mxelfl = nElements;    
+    double const pptmax = 1e11; // GeV
+    double const ef2dp3 = 0; // GeV, 0 means default is used
+    double const df2dp3 = -1; // default
+    bool const lprint = true;
+    auto mtflka = std::make_unique<int[]>(mxelfl);
+    char crvrck[8+1] = "76466879"; // magic number that FLUKA uses to see if it's the right version
+    
+    
+    /*
+     *    Iflxyz =  1 -> only inelastic
+     *    Iflxyz = 10 -> only elastic
+     *    Iflxyz = 11 -> inelastic + elastic
+     *    Iflxyz =100 -> only emd
+     *    Iflxyz =101 -> inelastic + emd
+     *    Iflxyz =110 -> elastic + emd
+     *    Iflxyz =111 -> inelastic + elastic + emd
+     */
+    int const iflxyz_ = 1;
+    
+    std::fill(&nelmfl[0], &nelmfl[nElements], 1);
+    std::fill(&wfelml[0], &wfelml[nElements], 1.);
+    std::transform(allElementsInUniverse.cbegin(), allElementsInUniverse.cend(),
+        std::back_inserter(izelfl), get_nucleus_Z);
+    
+    // call FLUKA
+    ::fluka::stpxyz_(&nElements, nelmfl.get(), izelfl.data(), wfelml.get(), &mxelfl,
+     &pptmax, &ef2dp3, &df2dp3, &iflxyz_, &lprint, mtflka.get(), crvrck);
+     
+    // now create & fill vector of (C8 Code, FLUKA mat. no.) pairs
+    std::vector<std::pair<Code, int>> mapping;
+    mapping.reserve(nElements);
+    
+    auto it = boost::make_zip_iterator(boost::make_tuple(
+                        allElementsInUniverse.begin(), &mtflka[0]));
+    auto end = boost::make_zip_iterator(boost::make_tuple(
+                        allElementsInUniverse.end(), &mtflka[nElements]));
+    for (; it != end; ++it) {
+        boost::tuple<Code const&, int&> tup = *it;        
+        mapping.emplace_back(tup.get<0>(), tup.get<1>());
+    }
+    
+    return mapping;
+
+    }
+}
diff --git a/corsika/modules/fluka/InteractionModel.hpp b/corsika/modules/fluka/InteractionModel.hpp
new file mode 100644
index 000000000..836ec2105
--- /dev/null
+++ b/corsika/modules/fluka/InteractionModel.hpp
@@ -0,0 +1,46 @@
+/*
+ * (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.
+ */
+
+#pragma once
+
+#include <corsika/media/Environment.hpp>
+#include <corsika/media/NuclearComposition.hpp>
+#include <corsika/framework/geometry/FourVector.hpp>
+#include <corsika/framework/utility/COMBoost.hpp>
+#include <corsika/framework/core/PhysicalUnits.hpp>
+
+namespace corsika::fluka {
+  class InteractionModel {
+    public:
+        template <typename TEnvironment>
+         InteractionModel(TEnvironment const&);
+        
+        CrossSectionType getCrossSection(
+      Code projectileId, Code targetId, FourMomentum const& projectileP4,
+      FourMomentum const& targetP4) const;
+      
+      bool isValid(Code projectileID, Code targetID, HEPEnergyType sqrtS) const;
+      
+      int getMaterialIndex(Code targetID) const;
+      
+      template <typename TSecondaryView>
+      void doInteraction(
+      TSecondaryView& view, Code const projectileId, Code const targetId,
+      FourMomentum const& projectileP4, FourMomentum const& targetP4);
+  
+    //~ static int const iflxyz_ = 1; //!< select interaction types (see fluka.h); hardcoded to inel. for now
+    private:
+        std::vector<std::pair<Code, int>> const materials_; //!< map target Code to FLUKA material no.
+        
+        template <typename TEnvironment>
+        static std::vector<std::pair<Code, int>> genFlukaMaterials(TEnvironment const&);
+    // TODO: random number stream
+  };
+}
+
+#include <corsika/detail/modules/fluka/InteractionModel.inl>
diff --git a/src/modules/fluka/CMakeLists.txt b/src/modules/fluka/CMakeLists.txt
new file mode 100644
index 000000000..8ce30ed94
--- /dev/null
+++ b/src/modules/fluka/CMakeLists.txt
@@ -0,0 +1,26 @@
+set (input_dir ${PROJECT_SOURCE_DIR}/src/modules/fluka)
+set (output_dir ${PROJECT_BINARY_DIR}/corsika/modules/fluka)
+
+file (MAKE_DIRECTORY ${output_dir})
+
+add_custom_command (
+  OUTPUT  ${output_dir}/Generated.inc
+  COMMAND ${input_dir}/code_generator.py 
+          ${PROJECT_BINARY_DIR}/corsika/framework/core/particle_db.pkl
+          ${input_dir}/fluka_codes.dat
+  DEPENDS ${input_dir}/code_generator.py
+          ${input_dir}/fluka_codes.dat
+          GenParticlesHeaders # for particle_db.pkl
+  WORKING_DIRECTORY
+          ${output_dir}/
+  COMMENT "Generate conversion tables for particle codes FLUKA <-> CORSIKA"
+  VERBATIM
+  )
+
+add_custom_target (SourceDirLinkFLUKA DEPENDS ${output_dir}/Generated.inc)
+add_dependencies (CORSIKA8 SourceDirLinkFLUKA)
+
+install (
+  FILES ${output_dir}/Generated.inc
+  DESTINATION include/corsika/modules/fluka
+  )
diff --git a/src/modules/fluka/code_generator.py b/src/modules/fluka/code_generator.py
new file mode 100755
index 000000000..690d1ca32
--- /dev/null
+++ b/src/modules/fluka/code_generator.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python3
+
+# (c) Copyright 2023 CORSIKA Project, corsika-project@lists.kit.edu
+#
+# See file AUTHORS for a list of contributors.
+#
+# 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.
+
+
+import pickle, sys, itertools
+
+
+def load_particledb(filename):
+    '''
+    loads the pickled particle_db (which is an OrderedDict)
+    '''
+    with open(filename, "rb") as f:
+        particle_db = pickle.load(f)
+    return particle_db
+
+
+def read_epos_codes(filename, particle_db):
+    '''
+    reads to epos codes data file
+
+    For particls known to epos, add 'epos_code' and 'epos_xsType' to particle_db
+    '''
+    with open(filename) as f:
+        for line in f:
+            line = line.strip()
+            if len(line) == 0 or line[0].startswith('#'):
+                continue
+            if len(line) == 2:
+                canInteractFlag = "no"
+                identifier, fluka_code = line.split()
+            else:
+                identifier, fluka_code, canInteractFlag = line.split()
+
+            try:
+                particle_db[identifier]["fluka_code"] = int(fluka_code)
+                particle_db[identifier]["fluka_canInteract"] = (canInteractFlag == "yes")
+            except KeyError as e:
+                raise Exception("Identifier '{:s}' not found in CORSIKA8 particle_db".format(identifier))
+
+
+def generate_fluka_enum(particle_db):
+    '''
+     generates the enum to access epos particles by readable names
+    '''
+    output = "enum class FLUKACode : int {\n"
+    for identifier, pData in particle_db.items():
+        if 'fluka_code' in pData:
+            output += "  {:s} = {:d},\n".format(identifier, pData['fluka_code'])
+    output += "};\n"
+    return output
+
+
+def generate_corsika2fluka(particle_db):    
+    '''
+    generates the look-up table to convert corsika codes to epos codes
+    '''
+    string = "std::array<FLUKACode, {:d}> constexpr corsika2epos = {{\n".format(len(particle_db))
+    for identifier, pData in particle_db.items():
+        if pData['isNucleus']: continue
+        if 'fluka_code' in pData:
+            string += "  FLUKACode::{:s}, \n".format(identifier)
+        else:
+            string += "  FLUKACode::Unknown, // {:s}\n".format(identifier + ' not implemented in FLUKA')
+    string += "};\n"
+    return string
+
+
+def generate_fluka_canInteract(particle_db):    
+    '''
+    generates the look-up table to convert corsika codes to epos codes
+    '''
+    string = "std::vector<bool> const flukaCanInteract = {\n"
+    for identifier, pData in particle_db.items():
+        canInteract = pData.get("fluka_canInteract", False)
+        string += "  {:s}, // {:s}\n".format(str(canInteract).lower(), "identifier")
+    string += "};\n"
+    return string
+
+
+if __name__ == "__main__":
+    if len(sys.argv) != 3:
+        print("usage: {:s} <particle_db.pkl> <fluka_codes.dat>".format(sys.argv[0]), file=sys.stderr)
+        sys.exit(1)
+        
+    print("code_generator.py for EPOS")
+    
+    particle_db = load_particledb(sys.argv[1])
+    read_fluka_codes(sys.argv[2], particle_db)
+    # ~ set_default_epos_definition(particle_db)
+    
+    with open("Generated.inc", "w") as f:
+        print("// this file is automatically generated\n// edit at your own risk!\n", file=f)
+        print(generate_fluka_enum(particle_db), file=f)
+        print(generate_corsika2fluka(particle_db), file=f)
+        print(generate_fluka_canInteract(particle_db), file=f)
diff --git a/src/modules/fluka/fluka_codes.dat b/src/modules/fluka/fluka_codes.dat
new file mode 100644
index 000000000..e2c1c319c
--- /dev/null
+++ b/src/modules/fluka/fluka_codes.dat
@@ -0,0 +1,123 @@
+Photon	7
+Electron	4
+Positron	3
+
+MuPlus	10
+MuMinus	11
+Pi0	23
+PiPlus	13	yes
+PiMinus	14	yes
+K0Long	12	yes
+KPlus	15	yes
+KMinus	16	yes
+Neutron	8	yes
+Proton	1	yes
+AntiProton	2	yes
+K0Short	19	yes
+Eta	0
+Lambda0	17	yes
+SigmaPlus	21
+Sigma0	22
+SigmaMinus	20
+Xi0	34
+XiMinus	36
+OmegaMinus	38
+AntiNeutron	9	yes
+Lambda0Bar	18	yes
+SigmaMinusBar	31
+Sigma0Bar	32
+SigmaPlusBar	33
+Xi0Bar	34
+XiPlusBar	37
+OmegaPlusBar	39
+
+Omega	0
+Rho0	0
+RhoPlus	0
+RhoRhoMinus	0
+DeltaPlusPlus	0
+DeltaPlus	0
+Delta0	0
+DeltaMinus	0
+DeltaMinusMinusBar	0
+DeltaMinusBar	0
+Delta0Bar	0
+DeltaPlusBar	0
+KStar0	0
+KStarPlus	0
+KStarMinusBar	0
+KStar0Bar	0
+NuE	5
+NuEBar	6
+NuMu	27
+NuMubar	28
+
+D0	16
+DPlus	25
+DMinusBar	24
+D0Bar	15
+DSPlus	0
+DSMinusBar	0
+EtaC	0
+DStar0	0
+DStarPlus	0
+DStarMinusBar	0
+DStar0Bar	0
+DStarSPlus	0
+DStarsMinusBar	0
+	0
+JPsi	0
+TauPlus	41
+TauMinus	42
+NuTau	43
+NuTauBar	44
+	0
+	0
+LambdaCPlus	17
+XiCPlus	34
+XiC0	36
+SigmaCPlus	21
+SigmaCPlus	22
+SigmaC0	20
+XiPrimeCPlus	34
+XiPrimeC0	36
+OmegaC0	38
+
+LambdaCMinusBar	18
+XiCMinusBar	35
+XiC0Bar	37
+SigmaCMinusMinusBar	31
+SigmaCMinusBar	32
+SigmaC0Bar	33
+XiPrimeCMinusBar	35
+XiPrimeC0Bar	37
+OmegaC0Bar	39
+
+SigmaStarCPlusPlus	0
+SigmaStarCPlus	0
+SigmaStarC0	0
+
+SigmaStarCBar--	0
+SigmaStarCMinusBar	0
+SigmaStarC0Bar	0
+
+B0	0
+BPlus	0
+BMinusBar	0
+B0Bar	0
+BS0	0
+BS0Bar	0
+BCPlus	0
+BCMinusBar	0
+LambdaB0	0
+SigmaBMinus	0
+SigmaBPlus	0
+XiB0	0
+XiBMinus	0
+OmegaBMinus	0
+LambdaB0Bar	0
+SigmaBPlusBar	0
+SigmaBMinusBar	0
+XiB0Bar	0
+XiBPlusBar	0
+OmegaBPlusBar	0
diff --git a/tests/modules/testFluka.cpp b/tests/modules/testFluka.cpp
new file mode 100644
index 000000000..96f265bf6
--- /dev/null
+++ b/tests/modules/testFluka.cpp
@@ -0,0 +1,38 @@
+/*
+ * (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/modules/fluka/InteractionModel.hpp>
+//~ #include <SetupTestEnvironment.hpp>
+
+#include <corsika/framework/geometry/Point.hpp>
+#include <corsika/framework/geometry/CoordinateSystem.hpp>
+
+#include <corsika/media/Environment.hpp>
+#include <corsika/media/IMediumModel.hpp>
+#include <corsika/media/HomogeneousMedium.hpp>
+
+#include <catch2/catch.hpp>
+
+using namespace corsika;
+
+TEST_CASE("FLUKA") {
+    using DummyEnvironmentInterface = IMediumModel;
+    using DummyEnvironment = Environment<DummyEnvironmentInterface>;
+    using MyHomogeneousModel = HomogeneousMedium<DummyEnvironmentInterface>;
+    
+    DummyEnvironment env;
+    auto& universe = *env.getUniverse();
+    CoordinateSystemPtr const& cs = env.getCoordinateSystem();
+    universe.setModelProperties<MyHomogeneousModel>(
+          1_kg / (1_m * 1_m * 1_m),
+          NuclearComposition(std::vector<Code>{Code::Hydrogen, Code::Oxygen, Code::Nitrogen, Code::Argon},
+          std::vector<double>{1./4., 1./4., 1./4., 1./4.}));
+
+    corsika::fluka::InteractionModel flukaModel{env};
+    
+}
-- 
GitLab