From fd16e22ae23c1122cdf0d5cb92bb0251afb5647d Mon Sep 17 00:00:00 2001
From: ralfulrich <ralf.ulrich@kit.edu>
Date: Tue, 24 Nov 2020 06:18:30 +0100
Subject: [PATCH] made many small details more robust, made a lot of testing

---
 Documentation/Examples/boundary_example.cc    |   2 +-
 Documentation/Examples/cascade_example.cc     |   6 +-
 .../Examples/cascade_proton_example.cc        |  25 ++--
 Documentation/Examples/em_shower.cc           |   4 +-
 Documentation/Examples/hybrid_MC.cc           |   2 +-
 Documentation/Examples/vertical_EAS.cc        |  10 +-
 Processes/TrackingLeapFrogCurved/Tracking.h   | 109 +++++++++++++-----
 Processes/TrackingLeapFrogStraight/Tracking.h |  89 +++++++++-----
 Processes/TrackingLine/Tracking.h             |   2 +
 Setup/SetupTrajectory.h                       |  31 ++++-
 10 files changed, 196 insertions(+), 84 deletions(-)

diff --git a/Documentation/Examples/boundary_example.cc b/Documentation/Examples/boundary_example.cc
index c40823c14..ed9d7bd43 100644
--- a/Documentation/Examples/boundary_example.cc
+++ b/Documentation/Examples/boundary_example.cc
@@ -101,7 +101,7 @@ int main() {
 
   // create "world" as infinite sphere filled with protons
   auto world = EnvType::CreateNode<Sphere>(
-      Point{rootCS, 0_m, 0_m, 0_m}, 1_km * std::numeric_limits<double>::infinity());
+      Point{rootCS, 0_m, 0_m, 0_m}, 100_km);
 
   using MyHomogeneousModel =
       environment::MediumPropertyModel<environment::UniformMagneticField<
diff --git a/Documentation/Examples/cascade_example.cc b/Documentation/Examples/cascade_example.cc
index 755c7c7c1..8b9b5b8ad 100644
--- a/Documentation/Examples/cascade_example.cc
+++ b/Documentation/Examples/cascade_example.cc
@@ -57,6 +57,8 @@ int main() {
 
   logging::SetLevel(logging::level::info);
 
+  C8LOG_INFO("vertical_EAS");
+
   std::cout << "cascade_example" << std::endl;
 
   const LengthType height_atmosphere = 112.8_km;
@@ -72,7 +74,7 @@ int main() {
   const CoordinateSystem& rootCS = env.GetCoordinateSystem();
 
   auto world = setup::Environment::CreateNode<Sphere>(
-      Point{rootCS, 0_m, 0_m, 0_m}, 1_km * std::numeric_limits<double>::infinity());
+      Point{rootCS, 0_m, 0_m, 0_m}, 150_km);
 
   using MyHomogeneousModel =
       environment::MediumPropertyModel<environment::UniformMagneticField<
@@ -110,7 +112,7 @@ int main() {
       rootCS, 0_m, 0_m,
       height_atmosphere); // this is the CORSIKA 7 start of atmosphere/universe
 
-  ShowerAxis const showerAxis{injectionPos, Vector{rootCS, 0_m, 0_m, -5000_km}, env};
+  ShowerAxis const showerAxis{injectionPos, Vector{rootCS, 0_m, 0_m, -100_km}, env};
 
   {
     auto elab2plab = [](HEPEnergyType Elab, HEPMassType m) {
diff --git a/Documentation/Examples/cascade_proton_example.cc b/Documentation/Examples/cascade_proton_example.cc
index 909cad004..b36239219 100644
--- a/Documentation/Examples/cascade_proton_example.cc
+++ b/Documentation/Examples/cascade_proton_example.cc
@@ -10,6 +10,7 @@
 #include <corsika/process/ProcessSequence.h>
 #include <corsika/process/hadronic_elastic_model/HadronicElasticModel.h>
 #include <corsika/process/stack_inspector/StackInspector.h>
+#include <corsika/process/energy_loss/EnergyLoss.h>
 
 #include <corsika/setup/SetupStack.h>
 #include <corsika/setup/SetupTrajectory.h>
@@ -17,6 +18,7 @@
 #include <corsika/environment/Environment.h>
 #include <corsika/environment/HomogeneousMedium.h>
 #include <corsika/environment/NuclearComposition.h>
+#include <corsika/environment/ShowerAxis.h>
 
 #include <corsika/geometry/Sphere.h>
 
@@ -59,7 +61,7 @@ using namespace corsika::units::si;
 //
 int main() {
 
-  logging::SetLevel(logging::level::trace);
+  logging::SetLevel(logging::level::info);
 
   std::cout << "cascade_proton_example" << std::endl;
 
@@ -73,20 +75,20 @@ int main() {
   auto& universe = *(env.GetUniverse());
   const CoordinateSystem& rootCS = env.GetCoordinateSystem();
 
-  auto theMedium = EnvType::CreateNode<Sphere>(
-      Point{rootCS, 0_m, 0_m, 0_m}, 1_km * std::numeric_limits<double>::infinity());
+  auto world = EnvType::CreateNode<Sphere>(
+      Point{rootCS, 0_m, 0_m, 0_m}, 150_km);
 
   using MyHomogeneousModel =
       environment::MediumPropertyModel<environment::UniformMagneticField<
           environment::HomogeneousMedium<setup::EnvironmentInterface>>>;
 
-  theMedium->SetModelProperties<MyHomogeneousModel>(
+  world->SetModelProperties<MyHomogeneousModel>(
       environment::Medium::AirDry1Atm, geometry::Vector(rootCS, 0_T, 0_T, 1_T),
       1_kg / (1_m * 1_m * 1_m),
       NuclearComposition(std::vector<particles::Code>{particles::Code::Hydrogen},
                          std::vector<float>{(float)1.}));
 
-  universe.AddChild(std::move(theMedium));
+  universe.AddChild(std::move(world));
 
   // setup particle stack, and add primary particle
   setup::Stack stack;
@@ -97,6 +99,7 @@ int main() {
   double theta = 0.;
   double phi = 0.;
 
+  Point injectionPos(rootCS, 0_m, 0_m, 0_m);
   {
     auto elab2plab = [](HEPEnergyType Elab, HEPMassType m) {
       return sqrt(Elab * Elab - m * m);
@@ -112,11 +115,10 @@ int main() {
     cout << "input particle: " << beamCode << endl;
     cout << "input angles: theta=" << theta << " phi=" << phi << endl;
     cout << "input momentum: " << plab.GetComponents() / 1_GeV << endl;
-    Point pos(rootCS, 0_m, 0_m, 0_m);
     stack.AddParticle(
         std::tuple<particles::Code, units::si::HEPEnergyType,
                    corsika::stack::MomentumVector, geometry::Point, units::si::TimeType>{
-            beamCode, E0, plab, pos, 0_ns});
+            beamCode, E0, plab, injectionPos, 0_ns});
   }
 
   // setup processes, decays and interactions
@@ -130,20 +132,19 @@ int main() {
   //  process::sibyll::NuclearInteraction sibyllNuc(env, sibyll);
   //  process::sibyll::Decay decay;
   process::pythia::Decay decay;
-  process::particle_cut::ParticleCut cut(20_GeV, true, true);
+  process::particle_cut::ParticleCut cut(60_GeV, true, true);
 
   // random::RNGManager::GetInstance().RegisterRandomStream("HadronicElasticModel");
   // process::HadronicElasticModel::HadronicElasticInteraction
   // hadronicElastic(env);
 
   process::track_writer::TrackWriter trackWriter("tracks.dat");
+  ShowerAxis const showerAxis{injectionPos, Vector{rootCS, 0_m, 0_m, -100_km}, env};
+  process::energy_loss::EnergyLoss eLoss{showerAxis, cut.GetECut()};
 
   // assemble all processes into an ordered process list
   // auto sequence = sibyll << decay << hadronicElastic << cut << trackWriter;
-  auto sequence = process::sequence(pythia, decay, cut, trackWriter, stackInspect);
-
-  // cout << "decltype(sequence)=" << type_id_with_cvr<decltype(sequence)>().pretty_name()
-  // << "\n";
+  auto sequence = process::sequence(pythia, decay, eLoss, cut, trackWriter, stackInspect);
 
   // define air shower object, run simulation
   cascade::Cascade EAS(env, tracking, sequence, stack);
diff --git a/Documentation/Examples/em_shower.cc b/Documentation/Examples/em_shower.cc
index 31298aa28..07c4cfb7b 100644
--- a/Documentation/Examples/em_shower.cc
+++ b/Documentation/Examples/em_shower.cc
@@ -71,13 +71,13 @@ int main(int argc, char** argv) {
   // setup environment, geometry
   using EnvType = setup::Environment;
   EnvType env;
-  const CoordinateSystem& rootCS = env.GetCoordinateSystem();
+  const CoordinateSystem& rootCS = env.GetCoordinateSystem(); 
   Point const center{rootCS, 0_m, 0_m, 0_m};
   auto builder = environment::make_layered_spherical_atmosphere_builder<
       setup::EnvironmentInterface,
       MyExtraEnv>::create(center, units::constants::EarthRadius::Mean,
                           environment::Medium::AirDry1Atm,
-                          geometry::Vector{rootCS, 0_T, 0_T, 1_T});
+                          geometry::Vector{rootCS, 0_T, 50_uT, 0_T});
   builder.setNuclearComposition(
       {{particles::Code::Nitrogen, particles::Code::Oxygen},
        {0.7847f, 1.f - 0.7847f}}); // values taken from AIRES manual, Ar removed for now
diff --git a/Documentation/Examples/hybrid_MC.cc b/Documentation/Examples/hybrid_MC.cc
index 75ed47a19..2216e4b33 100644
--- a/Documentation/Examples/hybrid_MC.cc
+++ b/Documentation/Examples/hybrid_MC.cc
@@ -101,7 +101,7 @@ int main(int argc, char** argv) {
       setup::EnvironmentInterface,
       MyExtraEnv>::create(center, units::constants::EarthRadius::Mean,
                           environment::Medium::AirDry1Atm,
-                          geometry::Vector{rootCS, 0_T, 0_T, 1_T});
+                          geometry::Vector{rootCS, 0_T, 50_uT, 0_T});
   builder.setNuclearComposition(
       {{particles::Code::Nitrogen, particles::Code::Oxygen},
        {0.7847f, 1.f - 0.7847f}}); // values taken from AIRES manual, Ar removed for now
diff --git a/Documentation/Examples/vertical_EAS.cc b/Documentation/Examples/vertical_EAS.cc
index 834989466..6fbed4a73 100644
--- a/Documentation/Examples/vertical_EAS.cc
+++ b/Documentation/Examples/vertical_EAS.cc
@@ -85,7 +85,7 @@ using MyExtraEnv = environment::MediumPropertyModel<environment::UniformMagnetic
 
 int main(int argc, char** argv) {
 
-  logging::SetLevel(logging::level::trace);
+  logging::SetLevel(logging::level::info);
 
   C8LOG_INFO("vertical_EAS");
 
@@ -110,7 +110,7 @@ int main(int argc, char** argv) {
       setup::EnvironmentInterface,
       MyExtraEnv>::create(center, units::constants::EarthRadius::Mean,
                           environment::Medium::AirDry1Atm,
-                          geometry::Vector{rootCS, 0_T, 5000_mT, 0_T});
+                          geometry::Vector{rootCS, 0_T, 50_uT, 0_T});
   builder.setNuclearComposition(
       {{particles::Code::Nitrogen, particles::Code::Oxygen},
        {0.7847f, 1.f - 0.7847f}}); // values taken from AIRES manual, Ar removed for now
@@ -256,9 +256,9 @@ int main(int argc, char** argv) {
                       EnergySwitch(55_GeV));
   auto decaySequence = process::sequence(decayPythia, decaySibyll);
   stack_inspector::StackInspector<setup::Stack> stackInspect(1000, false, E0);
-  auto sequence =
-    process::sequence(stackInspect, hadronSequence, reset_particle_mass, decaySequence,
-                        proposalCounted, em_continuous, cut, trackWriter, observationLevel, longprof);
+  auto sequence = process::sequence(stackInspect, hadronSequence, reset_particle_mass,
+                                    decaySequence, proposalCounted, em_continuous, cut,
+                                    trackWriter, observationLevel, longprof);
 
   // define air shower object, run simulation
   setup::Tracking tracking;
diff --git a/Processes/TrackingLeapFrogCurved/Tracking.h b/Processes/TrackingLeapFrogCurved/Tracking.h
index 9993f7b70..da636d656 100644
--- a/Processes/TrackingLeapFrogCurved/Tracking.h
+++ b/Processes/TrackingLeapFrogCurved/Tracking.h
@@ -39,6 +39,8 @@ namespace corsika::process {
      * \function LeapFrogStep
      *
      * Performs one leap-frog step consistent of two halve-steps with steplength/2
+     * The step is caluculated analytically precisely to reach to the next volume
+     *boundary.
      **/
     template <typename TParticle>
     auto LeapFrogStep(const TParticle& particle,
@@ -50,7 +52,7 @@ namespace corsika::process {
       } // charge of the particle
       const int chargeNumber = particle.GetChargeNumber();
       auto const* currentLogicalVolumeNode = particle.GetNode();
-      auto magneticfield =
+      MagneticFieldVector const& magneticfield =
           currentLogicalVolumeNode->GetModelProperties().GetMagneticField(
               particle.GetPosition());
       geometry::Vector<SpeedType::dimension_type> velocity =
@@ -85,6 +87,9 @@ namespace corsika::process {
     class Tracking : public corsika::process::tracking::Intersect<Tracking> {
 
     public:
+      Tracking()
+          : straightTracking_{tracking_line::Tracking()} {}
+
       template <typename TParticle>
       auto GetTrack(TParticle const& particle) {
         using namespace corsika::units::si;
@@ -111,23 +116,34 @@ namespace corsika::process {
         // maximum step-length since we need to follow curved
         // trajectories segment-wise -- at least if we don't employ concepts as "Helix
         // Trajectories" or similar
-        const auto& magneticfield =
+        MagneticFieldVector const& magneticfield =
             volumeNode.GetModelProperties().GetMagneticField(position);
-        const auto magnitudeB = magneticfield.norm();
-        const int chargeNumber = particle.GetChargeNumber();
-        auto const momentumVerticalMag =
-            particle.GetMomentum() -
-            particle.GetMomentum().parallelProjectionOnto(magneticfield);
+        corsika::units::si::MagneticFluxType const magnitudeB = magneticfield.norm();
+        int const chargeNumber = particle.GetChargeNumber();
+        bool const no_deflection = chargeNumber == 0 || magnitudeB == 0_T;
+
+        if (no_deflection) { return GetLinearTrajectory(particle); }
+
+        HEPMomentumType const pAlongB_delta =
+            (particle.GetMomentum() -
+             particle.GetMomentum().parallelProjectionOnto(magneticfield))
+                .norm();
+
+        if (pAlongB_delta == 0_GeV) {
+          // particle travel along, parallel to magnetic field. Rg is
+          // "0", but for purpose of step limit we return infinity here.
+          C8LOG_TRACE("pAlongB_delta is 0_GeV --> parallel");
+          return GetLinearTrajectory(particle);
+        }
+
         LengthType const gyroradius =
-            (chargeNumber == 0 || magnitudeB == 0_T
-                 ? std::numeric_limits<TimeType::value_type>::infinity() * 1_m
-                 : momentumVerticalMag.norm() * 1_V /
-                       (corsika::units::constants::c * abs(chargeNumber) * magnitudeB *
-                        1_eV));
+            (pAlongB_delta * 1_V /
+             (corsika::units::constants::c * abs(chargeNumber) * magnitudeB * 1_eV));
+
         const double maxRadians = 0.01;
         const LengthType steplimit = 2 * cos(maxRadians) * sin(maxRadians) * gyroradius;
         const TimeType steplimit_time = steplimit / initialVelocity.norm();
-        C8LOG_DEBUG("gyroradius {}, steplimit: {} m = {} s", gyroradius, steplimit,
+        C8LOG_DEBUG("gyroradius {}, steplimit: {} = {}", gyroradius, steplimit,
                     steplimit_time);
 
         // traverse the environment volume tree and find next
@@ -148,39 +164,48 @@ namespace corsika::process {
                                                const corsika::geometry::Sphere& sphere,
                                                const TMedium& medium) {
         using namespace corsika::units::si;
+
+        if (sphere.GetRadius() == 1_km * std::numeric_limits<double>::infinity()) {
+          return geometry::Intersections();
+        }
+
         const int chargeNumber = particle.GetChargeNumber();
         const auto& position = particle.GetPosition();
-        const auto& magneticfield = medium.GetMagneticField(position);
+        MagneticFieldVector const& magneticfield = medium.GetMagneticField(position);
+
+        const geometry::Vector<SpeedType::dimension_type> velocity =
+            particle.GetMomentum() / particle.GetEnergy() * corsika::units::constants::c;
+        const geometry::Vector<dimensionless_d> directionBefore =
+            velocity.normalized(); // determine steplength to next volume
 
-        if (chargeNumber == 0 || magneticfield.norm() == 0_T) {
+        auto const projectedDirection = directionBefore.cross(magneticfield);
+        auto const projectedDirectionSqrNorm = projectedDirection.GetSquaredNorm();
+        bool const isParallel = (projectedDirectionSqrNorm == 0 * square(1_T));
+
+        if (chargeNumber == 0 || magneticfield.norm() == 0_T || isParallel) {
           return tracking_line::Tracking::Intersect(particle, sphere, medium);
         }
 
         bool const numericallyInside = sphere.Contains(particle.GetPosition());
 
-        const geometry::Vector<SpeedType::dimension_type> velocity =
-            particle.GetMomentum() / particle.GetEnergy() * corsika::units::constants::c;
         const auto absVelocity = velocity.norm();
         auto energy = particle.GetEnergy();
         auto k = chargeNumber * corsika::units::constants::cSquared * 1_eV /
                  (absVelocity * energy * 1_V);
-        const geometry::Vector<dimensionless_d> directionBefore =
-            velocity.normalized(); // determine steplength to next volume
 
+        auto const denom =
+            (directionBefore.cross(magneticfield)).GetSquaredNorm() * k * k;
         const double a =
             ((directionBefore.cross(magneticfield)).dot(position - sphere.GetCenter()) *
                  k +
              1) *
-            4 /
-            (1_m * 1_m * (directionBefore.cross(magneticfield)).GetSquaredNorm() * k * k);
+            4 / (1_m * 1_m * denom);
         const double b = directionBefore.dot(position - sphere.GetCenter()) * 8 /
-                         ((directionBefore.cross(magneticfield)).GetSquaredNorm() * k *
-                          k * 1_m * 1_m * 1_m);
+                         (denom * 1_m * 1_m * 1_m);
         const double c = ((position - sphere.GetCenter()).GetSquaredNorm() -
                           (sphere.GetRadius() * sphere.GetRadius())) *
-                         4 /
-                         ((directionBefore.cross(magneticfield)).GetSquaredNorm() * k *
-                          k * 1_m * 1_m * 1_m * 1_m);
+                         4 / (denom * 1_m * 1_m * 1_m * 1_m);
+        C8LOG_TRACE("denom={}, a={}, b={}, c={}", denom, a, b, c);
         std::complex<double>* solutions = solve_quartic(0, a, b, c);
         LengthType d_enter, d_exit;
         int first = 0, first_entry = 0, first_exit = 0;
@@ -252,6 +277,38 @@ namespace corsika::process {
         throw std::runtime_error(
             "The Volume type provided is not supported in Intersect(particle, node)");
       }
+
+    protected:
+      /**
+       * Use internally stored class tracking_line::Tracking to
+       * perform a straight line tracking, if no magnetic bendig was
+       * detected.
+       *
+       */
+      template <typename TParticle>
+      auto GetLinearTrajectory(TParticle& particle) {
+
+        using namespace corsika::units::si;
+
+        // perform simple linear tracking
+        auto [straightTrajectory, minNode] = straightTracking_.GetTrack(particle);
+
+        // return as leap-frog trajectory
+        return std::make_tuple(
+            geometry::LeapFrogTrajectory(
+                straightTrajectory.GetLine().GetR0(),
+                straightTrajectory.GetLine().GetV0(),
+                MagneticFieldVector(particle.GetPosition().GetCoordinateSystem(), 0_T,
+                                    0_T, 0_T),
+                square(0_m) / (square(1_s) * 1_V),
+                straightTrajectory.GetDuration()), // trajectory
+            minNode);                              // next volume node
+      }
+
+    protected:
+      tracking_line::Tracking
+          straightTracking_; ///! we want this for neutral and B=0T tracks
+
     }; // namespace tracking_leapfrog_curved
 
   } // namespace tracking_leapfrog_curved
diff --git a/Processes/TrackingLeapFrogStraight/Tracking.h b/Processes/TrackingLeapFrogStraight/Tracking.h
index b88a6fc46..efa46981f 100644
--- a/Processes/TrackingLeapFrogStraight/Tracking.h
+++ b/Processes/TrackingLeapFrogStraight/Tracking.h
@@ -52,6 +52,19 @@ namespace corsika::process {
     class Tracking : public tracking_line::Tracking {
 
     public:
+      /**
+       * \param firstFraction fraction of first leap-frog halve step
+       * relative to full linear step to next volume boundary. This
+       * should not be less than 0.5, otherwise you risk that
+       * particles will never travel from one volume to the next
+       * one. A cross should be possible (even likely). If
+       * firstFraction is too big (~1) the resulting calculated error
+       * will be largest.
+       *
+       */
+      Tracking(double firstFraction = 0.55)
+          : firstFraction_(firstFraction) {}
+
       template <typename Particle>
       auto GetTrack(Particle& particle) {
         using namespace corsika::units::si;
@@ -72,39 +85,9 @@ namespace corsika::process {
 
         typedef decltype(particle.GetNode()) node_type;
         const node_type volumeNode = particle.GetNode();
-        auto magneticfield =
-            volumeNode->GetModelProperties().GetMagneticField(initialPosition);
-
-        // charge of the particle
-        const int chargeNumber = particle.GetChargeNumber();
-        const auto magnitudeB = magneticfield.GetNorm();
-        C8LOG_DEBUG("field={} uT, chargeNumber={}, magnitudeB={} uT",
-                    magneticfield.GetComponents() / 1_uT, chargeNumber, magnitudeB / 1_T);
-
-        // we need to limit maximum step-length since we absolutely
-        // need to follow strongly curved trajectories segment-wise,
-        // at least if we don't employ concepts as "Helix
-        // Trajectories" or similar
-        auto const momentumVerticalMag =
-            particle.GetMomentum() -
-            particle.GetMomentum().parallelProjectionOnto(magneticfield);
-        bool const no_deflection = chargeNumber == 0 || magnitudeB == 0_T;
-        LengthType const gyroradius =
-            (no_deflection ? std::numeric_limits<TimeType::value_type>::infinity() * 1_m
-                           : momentumVerticalMag.norm() * 1_V /
-                                 (corsika::units::constants::c * abs(chargeNumber) *
-                                  magnitudeB * 1_eV));
-        const double maxRadians = 0.01;
-        const LengthType steplimit = 2 * cos(maxRadians) * sin(maxRadians) * gyroradius;
-        C8LOG_DEBUG("gyroradius {}, Steplimit: {}", gyroradius, steplimit);
-
-        // calculate first halve step for "steplimit"
-        const auto initialMomentum = particle.GetMomentum();
-        const auto absMomentum = initialMomentum.norm();
-        const auto absVelocity = initialVelocity.norm();
-        const geometry::Vector<dimensionless_d> direction = initialVelocity.normalized();
 
         // check if particle is moving at all
+        const auto absVelocity = initialVelocity.norm();
         if (absVelocity * 1_s == 0_m) {
           return std::make_tuple(
               geometry::LineTrajectory(geometry::Line(initialPosition, initialVelocity),
@@ -112,6 +95,15 @@ namespace corsika::process {
               volumeNode);
         }
 
+        // charge of the particle, and magnetic field
+        const int chargeNumber = particle.GetChargeNumber();
+        auto magneticfield =
+            volumeNode->GetModelProperties().GetMagneticField(initialPosition);
+        const auto magnitudeB = magneticfield.GetNorm();
+        C8LOG_DEBUG("field={} uT, chargeNumber={}, magnitudeB={} uT",
+                    magneticfield.GetComponents() / 1_uT, chargeNumber, magnitudeB / 1_T);
+        bool const no_deflection = chargeNumber == 0 || magnitudeB == 0_T;
+
         // check, where the first halve-step direction has geometric intersections
         const auto [initialTrack, initialTrackNextVolume] =
             tracking_line::Tracking::GetTrack(particle);
@@ -128,9 +120,39 @@ namespace corsika::process {
           return std::make_tuple(initialTrack, initialTrackNextVolume);
         }
 
+	HEPMomentumType const pAlongB_delta =
+	  (particle.GetMomentum() -
+	   particle.GetMomentum().parallelProjectionOnto(magneticfield))
+	  .norm();
+
+        if (pAlongB_delta == 0_GeV) {
+          // particle travel along, parallel to magnetic field. Rg is
+          // "0", but for purpose of step limit we return infinity here.
+          C8LOG_TRACE("pAlongB_delta is 0_GeV --> parallel");
+          return std::make_tuple(initialTrack, initialTrackNextVolume);
+        }
+
+        LengthType const gyroradius =
+            (pAlongB_delta * 1_V /
+             (corsika::units::constants::c * abs(chargeNumber) * magnitudeB * 1_eV));
+	
+        // we need to limit maximum step-length since we absolutely
+        // need to follow strongly curved trajectories segment-wise,
+        // at least if we don't employ concepts as "Helix
+        // Trajectories" or similar
+        const double maxRadians = 0.01;
+        const LengthType steplimit = 2 * cos(maxRadians) * sin(maxRadians) * gyroradius;
+        C8LOG_DEBUG("gyroradius {}, Steplimit: {}", gyroradius, steplimit);
+
+	
+        // calculate first halve step for "steplimit"
+        const auto initialMomentum = particle.GetMomentum();
+        const auto absMomentum = initialMomentum.norm();
+        const geometry::Vector<dimensionless_d> direction = initialVelocity.normalized();
+
         // avoid any intersections within first halve steplength
         LengthType const firstHalveSteplength =
-            std::min(steplimit, initialTrackLength) / 2;
+            std::min(steplimit, initialTrackLength * firstFraction_);
 
         C8LOG_DEBUG("first halve step length {}, steplimit={}, initialTrackLength={}",
                     firstHalveSteplength, steplimit, initialTrackLength);
@@ -203,6 +225,9 @@ namespace corsika::process {
                 new_direction_normalized * absVelocity), // trajectory
             (switch_volume ? finalTrackNextVolume : volumeNode));
       }
+
+    protected:
+      double firstFraction_;
     };
 
   } // namespace tracking_leapfrog_straight
diff --git a/Processes/TrackingLine/Tracking.h b/Processes/TrackingLine/Tracking.h
index d9690ec0d..0dfac370a 100644
--- a/Processes/TrackingLine/Tracking.h
+++ b/Processes/TrackingLine/Tracking.h
@@ -35,6 +35,7 @@ namespace corsika::process {
     class Tracking : public corsika::process::tracking::Intersect<Tracking> {
 
     public:
+
       template <typename TParticle>
       auto GetTrack(TParticle const& particle) {
         using namespace corsika::units::si;
@@ -115,6 +116,7 @@ namespace corsika::process {
                                        1_s
                                  : n.dot(delta) / c);
       }
+
     };
 
   } // namespace tracking_line
diff --git a/Setup/SetupTrajectory.h b/Setup/SetupTrajectory.h
index a8b86e030..2a42db7a6 100644
--- a/Setup/SetupTrajectory.h
+++ b/Setup/SetupTrajectory.h
@@ -20,14 +20,39 @@
 
 namespace corsika::setup {
 
-  // Note: Tracking and Trajectory must fit together !
+  /**
+    Note/Warning:     Tracking and Trajectory must fit together !
+
+    tracking_leapfrog_curved::Tracking is the result of the Bachelor
+    thesis of Andre Schmidt, KIT. This is a leap-frog algorithm with
+    an analytical, precise calculation of volume intersections. This
+    algorithm needs a LeapFrogTrajectory.
+
+    tracking_leapfrog_straight::Tracking is a more simple and direct
+    leap-frog implementation. The two halve steps are coded explicitly
+    as two straight segments. Intersections with other volumes are
+    calculate only on the straight segments. This algorithm is based
+    on LineTrajectory.
+
+    tracking_line::Tracking is a pure straight tracker. It is based on
+    LineTrajectory.
+   */  
   typedef corsika::process::tracking_leapfrog_curved::Tracking Tracking;
-  // tracking_leapfrog_straight::Tracking tracking;
+  //typedef corsika::process::tracking_leapfrog_straight::Tracking Tracking;
+  //typedef corsika::process::tracking_line::Tracking Tracking;
 
   /// definition of Trajectory base class, to be used in tracking and cascades
-  // typedef corsika::geometry::LineTrajectory Trajectory;
+  //typedef corsika::geometry::LineTrajectory Trajectory;
   typedef corsika::geometry::LeapFrogTrajectory Trajectory;
 
+  /**
+
+     The following section is for unit testing only. Eventually it should
+     be moved to "tests".
+    
+    
+   */
+  
   namespace testing {
 
     template <typename TTrack>
-- 
GitLab