diff --git a/corsika/detail/framework/utility/CorsikaData.inl b/corsika/detail/framework/utility/CorsikaData.inl
index e2ea1e99bc3742783020312f725419fbd0d63846..fc638b81a7f2e1a7c66908835d06182d99e69610 100644
--- a/corsika/detail/framework/utility/CorsikaData.inl
+++ b/corsika/detail/framework/utility/CorsikaData.inl
@@ -7,14 +7,15 @@
  */
 #pragma once
 
+#include <boost/filesystem/path.hpp>
+
 #include <cstdlib>
 #include <stdexcept>
 #include <string>
 
-inline std::string corsika::corsika_data(std::string const& key) {
+inline boost::filesystem::path corsika::corsika_data(boost::filesystem::path const& key) {
   if (auto const* p = std::getenv("CORSIKA_DATA"); p != nullptr) {
-    auto const path = std::string(p) + "/" + key;
-    return path;
+    return boost::filesystem::path(p) / key;
   } else {
     throw std::runtime_error("CORSIKA_DATA not set");
   }
diff --git a/corsika/detail/modules/qgsjetII/Interaction.inl b/corsika/detail/modules/qgsjetII/Interaction.inl
index 828b9fcfeba8ae193ac622249a74ef317eaacc13..217fe30b72c3fa27d1ad3794a4a6b1ec8852ffd5 100644
--- a/corsika/detail/modules/qgsjetII/Interaction.inl
+++ b/corsika/detail/modules/qgsjetII/Interaction.inl
@@ -239,10 +239,14 @@ namespace corsika::qgsjetII {
       if (is_nucleus(targetCode)) { // nucleus
         targetMassNumber = get_nucleus_A(targetCode);
         if (targetMassNumber > maxMassNumber_)
-          throw std::runtime_error("QgsjetII target mass outside range.");
+          throw std::runtime_error(
+              "QgsjetII target mass outside range."); // LCOV_EXCL_LINE there is no
+                                                      // allowed path here
       } else {
-        if (targetCode != Proton::code)
-          throw std::runtime_error("QgsjetII Taget not possible.");
+        if (targetCode != Proton::code) // LCOV_EXCL_LINE there is no allowed path here
+          throw std::runtime_error(
+              "QgsjetII Taget not possible."); // LCOV_EXCL_LINE there is no allowed path
+                                               // here
       }
       CORSIKA_LOG_DEBUG("Interaction: target qgsjetII code/A: {}", targetMassNumber);
 
@@ -252,7 +256,9 @@ namespace corsika::qgsjetII {
       if (qgsjet_hadron_type == QgsjetIIHadronType::NucleusType) {
         projectileMassNumber = projectile.getNuclearA();
         if (projectileMassNumber > maxMassNumber_)
-          throw std::runtime_error("QgsjetII projectile mass outside range.");
+          throw std::runtime_error(
+              "QgsjetII projectile mass outside range."); // LCOV_EXCL_LINE there is no
+                                                          // allowed path here
         std::array<QgsjetIIHadronType, 2> constexpr nucleons = {
             QgsjetIIHadronType::ProtonType, QgsjetIIHadronType::NeutronType};
         std::uniform_int_distribution select(0, 1);
diff --git a/corsika/detail/modules/urqmd/UrQMD.inl b/corsika/detail/modules/urqmd/UrQMD.inl
index a1abaa6abf994b7f07a165b4f42599c3b4aaa89f..ee29642e00bbd408c355a5c2c645abef312564ec 100644
--- a/corsika/detail/modules/urqmd/UrQMD.inl
+++ b/corsika/detail/modules/urqmd/UrQMD.inl
@@ -15,15 +15,112 @@
 #include <corsika/framework/geometry/QuantityVector.hpp>
 #include <corsika/framework/geometry/Vector.hpp>
 
+#include <boost/filesystem.hpp>
+#include <boost/multi_array.hpp>
+
 #include <algorithm>
+#include <cassert>
 #include <functional>
 #include <iostream>
+#include <fstream>
+#include <sstream>
 
 #include <urqmd.hpp>
 
 namespace corsika::urqmd {
 
-  inline UrQMD::UrQMD() { ::urqmd::iniurqmdc8_(); }
+  inline UrQMD::UrQMD(boost::filesystem::path const& xs_file) {
+    readXSFile(xs_file);
+    ::urqmd::iniurqmdc8_();
+  }
+
+  inline CrossSectionType UrQMD::getTabulatedCrossSection(Code projectileCode,
+                                                          Code targetCode,
+                                                          HEPEnergyType labEnergy) const {
+    // translated to C++ from CORSIKA 7 subroutine cxtot_u
+
+    auto const kinEnergy = labEnergy - get_mass(projectileCode);
+
+    assert(kinEnergy >= HEPEnergyType::zero());
+
+    double const logKinEnergy = std::log10(kinEnergy * (1 / 1_GeV));
+    double const ye = std::max(10 * logKinEnergy + 10.5, 1.);
+    int const je = std::min(int(ye), int(xs_interp_support_table_.shape()[2] - 2));
+    std::array<double, 3> w;
+    w[2 - 1] = ye - je;
+    w[3 - 1] = w[2 - 1] * (w[2 - 1] - 1.) * .5;
+    w[1 - 1] = 1 - w[2 - 1] + w[3 - 1];
+    w[2 - 1] = w[2 - 1] - 2 * w[3 - 1];
+
+    int projectileIndex;
+    switch (projectileCode) {
+      case Code::Proton:
+        projectileIndex = 0;
+        break;
+      case Code::AntiProton:
+        projectileIndex = 1;
+        break;
+      case Code::Neutron:
+        projectileIndex = 2;
+        break;
+      case Code::AntiNeutron:
+        projectileIndex = 3;
+        break;
+      case Code::PiPlus:
+        projectileIndex = 4;
+        break;
+      case Code::PiMinus:
+        projectileIndex = 5;
+        break;
+      case Code::KPlus:
+        projectileIndex = 6;
+        break;
+      case Code::KMinus:
+        projectileIndex = 7;
+        break;
+      case Code::K0Short:
+      case Code::K0Long:
+      /* since K0Short and K0Long are treated the same, we can also add K0 and K0Bar
+       * to the list. This is a deviation from CORSIKA 7. */
+      case Code::K0:
+      case Code::K0Bar:
+        projectileIndex = 8;
+        break;
+      default: { // LCOV_EXCL_START since this can never happen due to canInteract
+        CORSIKA_LOG_WARN("UrQMD cross-section not tabulated for {}", projectileCode);
+        return CrossSectionType::zero(); // LCOV_EXCL_STOP
+      }
+    }
+
+    int targetIndex;
+    switch (targetCode) {
+      case Code::Nitrogen:
+        targetIndex = 0;
+        break;
+      case Code::Oxygen:
+        targetIndex = 1;
+        break;
+      case Code::Argon:
+        targetIndex = 2;
+        break;
+      default:
+        std::stringstream ss;
+        ss << "UrQMD cross-section not tabluated for target " << targetCode;
+        throw std::runtime_error(ss.str().data());
+    }
+
+    auto result = CrossSectionType::zero();
+    for (int i = 0; i < 3; ++i) {
+      result +=
+          xs_interp_support_table_[projectileIndex][targetIndex][je + i - 1 - 1] * w[i];
+    }
+
+    CORSIKA_LOG_TRACE(
+        "UrQMD::GetTabulatedCrossSection proj={}, targ={}, E={} GeV, sigma={}",
+        get_name(projectileCode), get_name(targetCode), labEnergy / 1_GeV, result);
+
+    return result;
+  }
 
   inline CrossSectionType UrQMD::getCrossSection(Code vProjectileCode, Code vTargetCode,
                                                  HEPEnergyType vLabEnergy,
@@ -63,20 +160,19 @@ namespace corsika::urqmd {
 
   template <typename TParticle> // need template here, as this is called both with
                                 // SetupParticle as well as SetupProjectile
-  inline CrossSectionType UrQMD::getCrossSection(TParticle const& vProjectile,
-                                                 Code vTargetCode) const {
-    // TODO: return 0 for non-hadrons?
-
-    auto const projectileCode = vProjectile.getPID();
-    auto const projectileEnergyLab = vProjectile.getEnergy();
-
-    if (projectileCode == Code::K0Long) {
-      return 0.5 * (getCrossSection(Code::K0, vTargetCode, projectileEnergyLab) +
-                    getCrossSection(Code::K0Bar, vTargetCode, projectileEnergyLab));
+  inline CrossSectionType UrQMD::getCrossSection(TParticle const& projectile,
+                                                 Code targetCode) const {
+    auto const projectileCode = projectile.getPID();
+
+    if (projectileCode == Code::Nucleus) {
+      /*
+       * unfortunately unavoidable at the moment until we have tools to get the actual
+       * inealstic cross-section from UrQMD
+       */
+      return CrossSectionType::zero();
     }
 
-    int const Ap = (projectileCode == Code::Nucleus) ? vProjectile.getNuclearA() : 1;
-    return getCrossSection(projectileCode, vTargetCode, projectileEnergyLab, Ap);
+    return getTabulatedCrossSection(projectileCode, targetCode, projectile.getEnergy());
   }
 
   inline bool UrQMD::canInteract(Code vCode) const {
@@ -85,10 +181,9 @@ namespace corsika::urqmd {
     // only the usual long-lived species as input.
     // TODO: Charmed mesons should be added to the list, too
 
-    static Code const validProjectileCodes[] = {
-        Code::Nucleus,     Code::Proton, Code::AntiProton, Code::Neutron,
-        Code::AntiNeutron, Code::PiPlus, Code::PiMinus,    Code::KPlus,
-        Code::KMinus,      Code::K0,     Code::K0Bar,      Code::K0Long};
+    static std::array constexpr validProjectileCodes{
+        Code::Proton,  Code::AntiProton, Code::Neutron, Code::AntiNeutron, Code::PiPlus,
+        Code::PiMinus, Code::KPlus,      Code::KMinus,  Code::K0Short,     Code::K0Long};
 
     return std::find(std::cbegin(validProjectileCodes), std::cend(validProjectileCodes),
                      vCode) != std::cend(validProjectileCodes);
@@ -301,4 +396,41 @@ namespace corsika::urqmd {
     return mapPDGToUrQMD.at(static_cast<int>(get_PDG(code)));
   }
 
+  inline void UrQMD::readXSFile(boost::filesystem::path const& filename) {
+    boost::filesystem::ifstream file(filename, std::ios::in);
+
+    if (!file.is_open()) {
+      throw std::runtime_error(
+          filename.native() +
+          " could not be opened."); // LCOV_EXCL_LINE since this is pointless to test
+    }
+
+    std::string line;
+
+    std::getline(file, line);
+    std::stringstream ss(line);
+
+    char dummy;
+    int nTargets, nProjectiles, nSupports;
+    ss >> dummy >> nTargets >> nProjectiles >> nSupports;
+
+    decltype(xs_interp_support_table_)::extent_gen extents;
+    xs_interp_support_table_.resize(extents[nProjectiles][nTargets][nSupports]);
+
+    for (int i = 0; i < nTargets; ++i) {
+      for (int j = 0; j < nProjectiles; ++j) {
+        for (int k = 0; k < nSupports; ++k) {
+          std::getline(file, line);
+          std::stringstream s(line);
+          double energy, sigma;
+          s >> energy >> sigma;
+          xs_interp_support_table_[j][i][k] = sigma * 1_mb;
+        }
+
+        std::getline(file, line);
+        std::getline(file, line);
+      }
+    }
+  }
+
 } // namespace corsika::urqmd
diff --git a/corsika/framework/utility/COMBoost.hpp b/corsika/framework/utility/COMBoost.hpp
index befd3025035105a184f33c1c2a872c750364665e..43a123cf18fa62fe62baded114b319525b856d55 100644
--- a/corsika/framework/utility/COMBoost.hpp
+++ b/corsika/framework/utility/COMBoost.hpp
@@ -19,14 +19,36 @@
 namespace corsika {
 
   /**
+     @defgroup Utilities
+
+     Collection of classes and methods to perform recurring tasks.
+   **/
+
+  /**
+     @class
+     @ingroup Utilities
+
      This utility class handles Lorentz boost between different
      referenence frames, using FourVector.
+
+     The class is initialized with projectile and optionally target
+     energy/momentum data. During initialization, a rotation matrix is
+     calculated to represent the projectile movement (and thus the
+     boost) along the z-axis. Also the inverse of this rotation is
+     calculated. The Lorentz boost matrix and its inverse are
+     determined as 2x2 matrices considering the energy and
+     pz-momentum.
+
+     Different constructors are offered with different specialization
+     for the cases of collisions (projectile-target) or just decays
+     (projectile only).
    */
 
   class COMBoost {
 
   public:
-    //! construct a COMBoost given four-vector of projectile and mass of target
+    //! construct a COMBoost given four-vector of projectile and mass of target (target at
+    //! rest)
     COMBoost(FourVector<HEPEnergyType, MomentumVector> const& Pprojectile,
              HEPEnergyType const massTarget);
 
@@ -41,9 +63,11 @@ namespace corsika {
     template <typename FourVector>
     FourVector fromCoM(FourVector const& p) const;
 
+    //! returns the rotated coordinate system
     CoordinateSystemPtr getRotatedCS() const;
 
   protected:
+    //! internal method
     void setBoost(double coshEta, double sinhEta);
 
   private:
diff --git a/corsika/framework/utility/CorsikaData.hpp b/corsika/framework/utility/CorsikaData.hpp
index d117db8da59e3399e77ff7304335c7e9eaa21711..2ab2dec7dabcf7a81bcbe976d689bd92f72df1b3 100644
--- a/corsika/framework/utility/CorsikaData.hpp
+++ b/corsika/framework/utility/CorsikaData.hpp
@@ -8,13 +8,18 @@
 
 #pragma once
 
-#include <string>
+#include <boost/filesystem/path.hpp>
 
 namespace corsika {
   /**
+   * @file CorsikaData.hpp
+   * @ingroup Utilities
+   * @{
    * returns the full path of the file \p filename within the CORSIKA_DATA directory
    */
-  std::string corsika_data(std::string const& filename);
+  boost::filesystem::path corsika_data(boost::filesystem::path const& filename);
+
+  //! @}
 } // namespace corsika
 
 #include <corsika/detail/framework/utility/CorsikaData.inl>
diff --git a/corsika/framework/utility/QuarticSolver.hpp b/corsika/framework/utility/QuarticSolver.hpp
index 0bcdfcedf1a9a9055306acacfd756012726cf8b0..ad9484939d10691e38c7e74d9e2632b3d2e60463 100644
--- a/corsika/framework/utility/QuarticSolver.hpp
+++ b/corsika/framework/utility/QuarticSolver.hpp
@@ -11,6 +11,9 @@
 #include <complex>
 
 /**
+ * @file QuarticSolver.hpp
+ * @ingroup Utilities
+ * @{
  * \todo convert to class
  */
 
@@ -56,6 +59,8 @@ namespace corsika::quartic_solver {
   // afterwards.
   DComplex* solve_quartic(double a, double b, double c, double d);
 
+  //! @}
+
 } // namespace corsika::quartic_solver
 
 #include <corsika/detail/framework/utility/QuarticSolver.inl>
diff --git a/corsika/framework/utility/SaveBoostHistogram.hpp b/corsika/framework/utility/SaveBoostHistogram.hpp
index 14e0379966c447cd0c2f8cfa7ccffcf3c29a0478..30cbdda2a6c9e95ce63dbfbaf7c7a120d4bbe727 100644
--- a/corsika/framework/utility/SaveBoostHistogram.hpp
+++ b/corsika/framework/utility/SaveBoostHistogram.hpp
@@ -13,6 +13,8 @@
 namespace corsika {
 
   /**
+   * @ingroup Utilities
+   *
    * This functions saves a boost::histogram into a numpy file. Only rather basic axis
    * types are supported: regular, variable, integer, category<int>. Only "ordinary" bin
    * counts (i.e. a double or int) are supported, nothing fancy like profiles.
diff --git a/corsika/modules/urqmd/UrQMD.hpp b/corsika/modules/urqmd/UrQMD.hpp
index 8f2fd8edeed26f84be2b6049fb760c26b4729ac4..3f6d3e9936f1fd13fcb831359f8faee6768f2948 100644
--- a/corsika/modules/urqmd/UrQMD.hpp
+++ b/corsika/modules/urqmd/UrQMD.hpp
@@ -12,21 +12,28 @@
 #include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/process/InteractionProcess.hpp>
 #include <corsika/framework/random/RNGManager.hpp>
+#include <corsika/framework/utility/CorsikaData.hpp>
 
 #include <corsika/setup/SetupStack.hpp>
 
+#include <boost/filesystem/path.hpp>
+#include <boost/multi_array.hpp>
+
 #include <array>
 #include <utility>
+#include <string>
 
 namespace corsika::urqmd {
 
   class UrQMD : public InteractionProcess<UrQMD> {
   public:
-    UrQMD();
+    UrQMD(boost::filesystem::path const& path = corsika_data("UrQMD/UrQMD-1.3.1-xs.dat"));
 
     template <typename TParticle>
     GrammageType getInteractionLength(TParticle const&) const;
 
+    CrossSectionType getTabulatedCrossSection(Code, Code, HEPEnergyType) const;
+
     template <typename TParticle>
     CrossSectionType getCrossSection(TParticle const&, Code) const;
 
@@ -39,11 +46,12 @@ namespace corsika::urqmd {
 
   private:
     static CrossSectionType getCrossSection(Code, Code, HEPEnergyType, int);
+    void readXSFile(boost::filesystem::path const&);
 
     // data members
     default_prng_type& RNG_ = RNGManager::getInstance().getRandomStream("urqmd");
-
     std::uniform_int_distribution<int> booleanDist_{0, 1};
+    boost::multi_array<CrossSectionType, 3> xs_interp_support_table_;
   };
 
   /**
diff --git a/documentation/index.rst b/documentation/index.rst
index c1be80a37ee4a481cd3c67638772b11309c5a5d3..79440c503bd0b500d8235d5c1edd77c2bf459822 100644
--- a/documentation/index.rst
+++ b/documentation/index.rst
@@ -13,6 +13,7 @@ Welcome to the CORSIKA 8 air shower simulation framework.
    units
    environment
    stack
+   utilities
    api
    
 
diff --git a/documentation/utilities.rst b/documentation/utilities.rst
new file mode 100644
index 0000000000000000000000000000000000000000..37c289fde6a68f36548ff2fcdab0ce0dbda2114b
--- /dev/null
+++ b/documentation/utilities.rst
@@ -0,0 +1,9 @@
+Utilities
+==========
+
+.. doxygengroup:: Utilities
+   :project: CORSIKA8
+   :members:
+
+
+      
diff --git a/modules/urqmd/urqmdInterface.F b/modules/urqmd/urqmdInterface.F
index ec2bd092a8386393368a6789df73b3efa3253160..446221dfc2b2f311b11a4c0080e42ba02fab7296 100644
--- a/modules/urqmd/urqmdInterface.F
+++ b/modules/urqmd/urqmdInterface.F
@@ -432,16 +432,3 @@ c~       xsegymin=0.25d0
 
       end
 
-c
-c M. Reininghaus, 2020-04-08
-c
-      integer function ReadSigmaLn(ia, ib, ic)
-      implicit none
-
-      include 'comres.f'
-
-      integer :: ia, ib, ic
-
-      ReadSigmaLn = SigmaLn(ia, ib, ic)
-
-      end function ReadSigmaLn
diff --git a/tests/modules/testQGSJetII.cpp b/tests/modules/testQGSJetII.cpp
index 477dedc6ca15a123b26f5524cbeb888fb2700cd7..4a604fd8bbf32f440db2eb0e073aed684b412fe4 100644
--- a/tests/modules/testQGSJetII.cpp
+++ b/tests/modules/testQGSJetII.cpp
@@ -175,7 +175,7 @@ TEST_CASE("QgsjetIIInterface", "[processes]") {
   SECTION("InteractionInterface Nuclei") {
 
     auto [stackPtr, secViewPtr] = setup::testing::setup_stack(
-        Code::Nucleus, 20, 10, 10100_GeV,
+        Code::Nucleus, 60, 30, 20100_GeV,
         (setup::Environment::BaseNodeType* const)nodePtr, *csPtr);
     setup::StackView& view = *(secViewPtr.get());
     auto particle = stackPtr->first();
@@ -183,10 +183,15 @@ TEST_CASE("QgsjetIIInterface", "[processes]") {
     auto const projectileMomentum = projectile.getMomentum();
 
     corsika::qgsjetII::Interaction model;
-    model.doInteraction(view);
+    model.doInteraction(view); // this also should produce some fragments
+    CHECK(view.getSize() == Approx(188).margin(2)); // this is not physics validation
+    int countFragments = 0;
+    for (auto const& sec : view) { countFragments += (sec.getPID() == Code::Nucleus); }
+    CHECK(countFragments == Approx(2).margin(1)); // this is not physics validation
     [[maybe_unused]] const GrammageType length = model.getInteractionLength(particle);
 
-    CHECK(length / (1_g / square(1_cm)) == Approx(20.13).margin(0.1));
+    CHECK(length / (1_g / square(1_cm)) ==
+          Approx(12).margin(2)); // this is not physics validation
   }
 
   SECTION("Heavy nuclei") {
@@ -210,17 +215,50 @@ TEST_CASE("QgsjetIIInterface", "[processes]") {
   }
 
   SECTION("Allowed Particles") {
-
-    auto [stackPtr, secViewPtr] = setup::testing::setup_stack(
-        Code::Electron, 0, 0, 1100_GeV, (setup::Environment::BaseNodeType* const)nodePtr,
-        *csPtr);
-    auto particle = stackPtr->first();
-    auto projectile = secViewPtr->getProjectile();
-    auto const projectileMomentum = projectile.getMomentum();
-
-    corsika::qgsjetII::Interaction model;
-
-    GrammageType const length = model.getInteractionLength(particle);
-    CHECK(length / (1_g / square(1_cm)) == std::numeric_limits<double>::infinity());
+    { // electron
+      auto [stackPtr, secViewPtr] = setup::testing::setup_stack(
+          Code::Electron, 0, 0, 100_GeV, (setup::Environment::BaseNodeType* const)nodePtr,
+          *csPtr);
+      auto particle = stackPtr->first();
+      corsika::qgsjetII::Interaction model;
+      GrammageType const length = model.getInteractionLength(particle);
+      CHECK(length / (1_g / square(1_cm)) == std::numeric_limits<double>::infinity());
+    }
+    { // pi0 is internally converted into pi+/pi-
+      auto [stackPtr, secViewPtr] = setup::testing::setup_stack(
+          Code::Pi0, 0, 0, 1000_GeV, (setup::Environment::BaseNodeType* const)nodePtr,
+          *csPtr);
+      setup::StackView& view = *(secViewPtr.get());
+      corsika::qgsjetII::Interaction model;
+      model.doInteraction(view);
+      CHECK(view.getSize() == Approx(18).margin(2)); // this is not physics validation
+    }
+    { // rho0 is internally converted into pi-/pi+
+      auto [stackPtr, secViewPtr] = setup::testing::setup_stack(
+          Code::Rho0, 0, 0, 1000_GeV, (setup::Environment::BaseNodeType* const)nodePtr,
+          *csPtr);
+      setup::StackView& view = *(secViewPtr.get());
+      corsika::qgsjetII::Interaction model;
+      model.doInteraction(view);
+      CHECK(view.getSize() == Approx(7).margin(2)); // this is not physics validation
+    }
+    { // Lambda is internally converted into neutron
+      auto [stackPtr, secViewPtr] = setup::testing::setup_stack(
+          Code::Lambda0, 0, 0, 100_GeV, (setup::Environment::BaseNodeType* const)nodePtr,
+          *csPtr);
+      setup::StackView& view = *(secViewPtr.get());
+      corsika::qgsjetII::Interaction model;
+      model.doInteraction(view);
+      CHECK(view.getSize() == Approx(25).margin(3)); // this is not physics validation
+    }
+    { // AntiLambda is internally converted into anti neutron
+      auto [stackPtr, secViewPtr] = setup::testing::setup_stack(
+          Code::Lambda0Bar, 0, 0, 100_GeV,
+          (setup::Environment::BaseNodeType* const)nodePtr, *csPtr);
+      setup::StackView& view = *(secViewPtr.get());
+      corsika::qgsjetII::Interaction model;
+      model.doInteraction(view);
+      CHECK(view.getSize() == Approx(25).margin(3)); // this is not physics validation
+    }
   }
 }
diff --git a/tests/modules/testUrQMD.cpp b/tests/modules/testUrQMD.cpp
index 28ed39a159c9ceb8bb0960d8adac735fc67a95a2..0f9f4a86243ec0f7f5b0c16e989b9459a5e0a293 100644
--- a/tests/modules/testUrQMD.cpp
+++ b/tests/modules/testUrQMD.cpp
@@ -77,24 +77,41 @@ TEST_CASE("UrQMD") {
     auto const& cs = *csPtr;
     { [[maybe_unused]] auto const& env_dummy = env; }
 
-    Code validProjectileCodes[] = {Code::PiPlus,  Code::PiMinus, Code::Proton,
-                                   Code::Neutron, Code::KPlus,   Code::KMinus,
-                                   Code::K0,      Code::K0Bar,   Code::K0Long};
+    Code validProjectileCodes[] = {Code::PiPlus,     Code::PiMinus,     Code::Proton,
+                                   Code::AntiProton, Code::AntiNeutron, Code::Neutron,
+                                   Code::KPlus,      Code::KMinus,      Code::K0,
+                                   Code::K0Bar,      Code::K0Long};
 
     for (auto code : validProjectileCodes) {
-      auto [stack, view] = setup::testing::setup_stack(
-          code, 0, 0, 100_GeV, (setup::Environment::BaseNodeType* const)nodePtr, cs);
+      auto [stack, view] = setup::testing::setup_stack(code, 0, 0, 100_GeV, nodePtr, cs);
       CHECK(stack->getEntries() == 1);
       CHECK(view->getEntries() == 0);
 
       // simple check whether the cross-section is non-vanishing
-      CHECK(urqmd.getCrossSection(view->getProjectile(), Code::Proton) / 1_mb > 0);
-      CHECK(urqmd.getCrossSection(view->getProjectile(), Code::Nitrogen) / 1_mb > 0);
-      CHECK(urqmd.getCrossSection(view->getProjectile(), Code::Oxygen) / 1_mb > 0);
-      CHECK(urqmd.getCrossSection(view->getProjectile(), Code::Argon) / 1_mb > 0);
+      // only nuclei with available tabluated data so far
+      CHECK(urqmd.getInteractionLength(stack->getNextParticle()) > 1_g / square(1_cm));
     }
   }
 
+  SECTION("targets options") {
+    auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Argon);
+    auto const& cs = *csPtr;
+    { [[maybe_unused]] auto const& env_dummy = env; }
+    auto [stack, view] =
+        setup::testing::setup_stack(Code::Proton, 0, 0, 100_GeV, nodePtr, cs);
+    CHECK(urqmd.getInteractionLength(stack->getNextParticle()) / 1_g * square(1_cm) ==
+          Approx(105).margin(5));
+  }
+
+  SECTION("invalid targets options") {
+    auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Omega);
+    auto const& cs = *csPtr;
+    { [[maybe_unused]] auto const& env_dummy = env; }
+    auto [stack, view] =
+        setup::testing::setup_stack(Code::Neutron, 0, 0, 100_GeV, nodePtr, cs);
+    CHECK_THROWS(urqmd.getInteractionLength(stack->getNextParticle()));
+  }
+
   SECTION("nucleus projectile") {
     auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Oxygen);
     [[maybe_unused]] auto const& env_dummy = env;      // against warnings
@@ -146,6 +163,45 @@ TEST_CASE("UrQMD") {
           Approx(0).margin(1e-2));
   }
 
+  SECTION("\"special\" projectile and target") {
+    {
+      auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Proton);
+      [[maybe_unused]] auto const& env_dummy = env;      // against warnings
+      [[maybe_unused]] auto const& node_dummy = nodePtr; // against warnings
+
+      auto [stackPtr, secViewPtr] = setup::testing::setup_stack(
+          Code::PiPlus, 0, 0, 40_GeV, (setup::Environment::BaseNodeType* const)nodePtr,
+          *csPtr);
+      CHECK_THROWS(urqmd.doInteraction(*secViewPtr)); // Code::Proton not a valid target
+    }
+
+    {
+      auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Oxygen);
+      [[maybe_unused]] auto const& env_dummy = env;      // against warnings
+      [[maybe_unused]] auto const& node_dummy = nodePtr; // against warnings
+
+      auto [stackPtr, secViewPtr] = setup::testing::setup_stack(
+          Code::PiPlus, 0, 0, 40_GeV, (setup::Environment::BaseNodeType* const)nodePtr,
+          *csPtr);
+      CHECK(stackPtr->getEntries() == 1);
+      CHECK(secViewPtr->getEntries() == 0);
+
+      // must be assigned to variable, cannot be used as rvalue?!
+      auto projectile = secViewPtr->getProjectile();
+      auto const projectileMomentum = projectile.getMomentum();
+
+      urqmd.doInteraction(*secViewPtr);
+
+      CHECK(sumCharge(*secViewPtr) ==
+            get_charge_number(Code::PiPlus) + get_charge_number(Code::Oxygen));
+
+      auto const secMomSum =
+          sumMomentum(*secViewPtr, projectileMomentum.getCoordinateSystem());
+      CHECK((secMomSum - projectileMomentum).getNorm() / projectileMomentum.getNorm() ==
+            Approx(0).margin(1e-2));
+    }
+  }
+
   SECTION("K0Long projectile") {
     auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Oxygen);
     [[maybe_unused]] auto const& env_dummy = env;      // against warnings
@@ -171,4 +227,22 @@ TEST_CASE("UrQMD") {
     CHECK((secMomSum - projectileMomentum).getNorm() / projectileMomentum.getNorm() ==
           Approx(0).margin(1e-2));
   }
+
+  SECTION("K0Short projectile") {
+    auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Argon);
+    [[maybe_unused]] auto const& env_dummy = env;      // against warnings
+    [[maybe_unused]] auto const& node_dummy = nodePtr; // against warnings
+
+    auto [stackPtr, secViewPtr] = setup::testing::setup_stack(
+        Code::K0Short, 0, 0, 40_GeV, (setup::Environment::BaseNodeType* const)nodePtr,
+        *csPtr);
+    CHECK(stackPtr->getEntries() == 1);
+    CHECK(secViewPtr->getEntries() == 0);
+
+    // must be assigned to variable, cannot be used as rvalue?!
+    auto projectile = secViewPtr->getProjectile();
+    auto const projectileMomentum = projectile.getMomentum();
+
+    CHECK_THROWS(urqmd.doInteraction(*secViewPtr));
+  }
 }