diff --git a/Framework/Cascade/Cascade_interpolation.h b/Framework/Cascade/Cascade_interpolation.h new file mode 100644 index 0000000000000000000000000000000000000000..dccd3f8e705ca264fa27e7b637bdc4ab332b74b8 --- /dev/null +++ b/Framework/Cascade/Cascade_interpolation.h @@ -0,0 +1,341 @@ +/* + * (c) Copyright 2018 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. + */ + +#ifndef _include_corsika_cascade_Cascade_h_ +#define _include_corsika_cascade_Cascade_h_ + +#include <corsika/environment/Environment.h> +#include <corsika/process/ProcessReturn.h> +#include <corsika/random/ExponentialDistribution.h> +#include <corsika/random/RNGManager.h> +#include <corsika/random/UniformRealDistribution.h> +#include <corsika/stack/SecondaryView.h> +#include <corsika/units/PhysicalUnits.h> + +#include <corsika/setup/SetupTrajectory.h> + +/* see Issue 161, we need to include SetupStack only because we need + to globally define StackView. This is clearly not nice and should + be changed, when possible. It might be that StackView needs to be + templated in Cascade, but this would be even worse... so we don't + do that until it is really needed. + */ +#include <corsika/setup/SetupStack.h> + +#include <cassert> +#include <cmath> +#include <iostream> +#include <limits> +#include <type_traits> + +#include <boost/type_index.hpp> +using boost::typeindex::type_id_with_cvr; + +/** + * The cascade namespace assembles all objects needed to simulate full particles cascades. + */ + +namespace corsika::cascade { + + /** + * \class Cascade + * + * The Cascade class is constructed from template arguments making + * it very versatile. Via the template arguments physics models are + * plugged into the cascade simulation. + * + * <b>TTracking</b> must be a class according to the + * TrackingInterface providing the functions: + * <code>auto GetTrack(Particle const& p)</auto>, + * with the return type <code>geometry::Trajectory<corsika::geometry::Line> + * </code> + * + * <b>TProcessList</b> must be a ProcessSequence. * + * <b>Stack</b> is the storage object for particle data, i.e. with + * Particle class type <code>Stack::ParticleType</code> + * + * + */ + + template <typename TTracking, typename TProcessList, typename TStack, + /* + TStackView is needed as template parameter because of issue 161 and the + inability of clang to understand "MakeView" so far. + */ + typename TStackView = corsika::setup::StackView> + class Cascade { + using Particle = typename TStack::ParticleType; + using VolumeTreeNode = + std::remove_pointer_t<decltype(((Particle*)nullptr)->GetNode())>; + using MediumInterface = typename VolumeTreeNode::IModelProperties; + + // we only want fully configured objects + Cascade() = delete; + + public: + /** + * Cascade class cannot be default constructed, but needs a valid + * list of physics processes for configuration at construct time. + */ + Cascade(corsika::environment::Environment<MediumInterface> const& env, TTracking& tr, + TProcessList& pl, TStack& stack) + : fEnvironment(env) + , fTracking(tr) + , fProcessSequence(pl) + , fStack(stack) {} + + /** + * The Init function is called before the actual cascade simulations. + * All components of the Cascade simulation must be configured here. + */ + void Init() { + fProcessSequence.Init(); + fStack.Init(); + } + + /** + * set the nodes for all particles on the stack according to their numerical + * position + */ + void SetNodes() { + std::for_each(fStack.begin(), fStack.end(), [&](auto& p) { + auto const* numericalNode = + fEnvironment.GetUniverse()->GetContainingNode(p.GetPosition()); + p.SetNode(numericalNode); + }); + } + + /** + * The Run function is the main simulation loop, which processes + * particles from the Stack until the Stack is empty. + */ + void Run() { + SetNodes(); + + while (!fStack.IsEmpty()) { + while (!fStack.IsEmpty()) { + auto pNext = fStack.GetNextParticle(); + std::cout << "========= next: " << pNext.GetPID() << std::endl; + Step(pNext); + std::cout << "========= stack ============" << std::endl; + fProcessSequence.DoStack(fStack); + } + // do cascade equations, which can put new particles on Stack, + // thus, the double loop + // DoCascadeEquations(); + } + } + + /** + * Force an interaction of the top particle of the stack at its current position. + * Note that SetNodes() or an equivalent procedure needs to be called first if you + * want to call forceInteraction() for the primary interaction. + */ + void forceInteraction() { + std::cout << "forced interaction!" << std::endl; + auto vParticle = fStack.GetNextParticle(); + TStackView secondaries(vParticle); + auto projectile = secondaries.GetProjectile(); + interaction(vParticle, projectile); + fProcessSequence.DoSecondaries(secondaries); + vParticle.Delete(); // todo: this should be reviewed, see below + } + + private: + /** + * The Step function is executed for each particle from the + * stack. It will calcualte geometric transport of the particles, + * and apply continuous and stochastic processes to it, which may + * lead to energy losses, scattering, absorption, decays and the + * production of secondary particles. + * + * New particles produced in one step are subject to further + * processing, e.g. thinning, etc. + */ + void Step(Particle& vParticle) { + using namespace corsika; + using namespace corsika::units::si; + + // determine combined total interaction length (inverse) + InverseGrammageType const total_inv_lambda = + fProcessSequence.GetTotalInverseInteractionLength(vParticle); + + // sample random exponential step length in grammage + corsika::random::ExponentialDistribution expDist(1 / total_inv_lambda); + GrammageType const next_interact = expDist(fRNG); + + std::cout << "total_inv_lambda=" << total_inv_lambda + << ", next_interact=" << next_interact << std::endl; + + auto const* currentLogicalNode = vParticle.GetNode(); + + // assert that particle stays outside void Universe if it has no + // model properties set + assert(currentLogicalNode != &*fEnvironment.GetUniverse() || + fEnvironment.GetUniverse()->HasModelProperties()); + + // determine combined total inverse decay time + InverseTimeType const total_inv_lifetime = + fProcessSequence.GetTotalInverseLifetime(vParticle); + + // sample random exponential decay time + corsika::random::ExponentialDistribution expDistDecay(1 / total_inv_lifetime); + TimeType const next_decay = expDistDecay(fRNG); + std::cout << "total_inv_lifetime=" << total_inv_lifetime + << ", next_decay=" << next_decay << std::endl; + + // convert next_decay from time to length [m] + LengthType const distance_decay = next_decay * vParticle.GetMomentum().norm() / + vParticle.GetEnergy() * units::constants::c; + + // determine geometric tracking + auto [step, geomMaxLength, nextVol, magMaxLength, directionBefore, directionAfter] = fTracking.GetTrack(vParticle); + [[maybe_unused]] auto const& dummy_nextVol = nextVol; + + // convert next_step from grammage to length + LengthType const distance_interact = + currentLogicalNode->GetModelProperties().ArclengthFromGrammage(step, + next_interact); + + // determine the maximum geometric step length + LengthType const distance_max = fProcessSequence.MaxStepLength(vParticle, step); + std::cout << "distance_max=" << distance_max << std::endl; + + // take minimum of geometry, interaction, decay for next step + auto const min_distance = std::min( + {distance_interact, distance_decay, distance_max, geomMaxLength, magMaxLength}); + + std::cout << " move particle by : " << min_distance << std::endl; + + // here the particle is actually moved along the trajectory to new position: + // std::visit(setup::ParticleUpdate<Particle>{vParticle}, step); + vParticle.SetPosition(step.PositionFromArclength(min_distance)); + // .... also update time, momentum, direction, ... + vParticle.SetMomentum((directionBefore * (1 - min_distance / magMaxLength) + + directionAfter * min_distance /magMaxLength) * vParticle.GetMomentum().GetNorm()); + vParticle.SetTime(vParticle.GetTime() + min_distance / units::constants::c); + + step.LimitEndTo(min_distance); + + // apply all continuous processes on particle + track + process::EProcessReturn status = fProcessSequence.DoContinuous(vParticle, step); + + if (status == process::EProcessReturn::eParticleAbsorbed) { + std::cout << "Cascade: delete absorbed particle " << vParticle.GetPID() << " " + << vParticle.GetEnergy() / 1_GeV << "GeV" << std::endl; + vParticle.Delete(); + return; + } + + std::cout << "sth. happening before geometric limit ? " + << ((min_distance < geomMaxLength) ? "yes" : "no") << std::endl; + + if (min_distance < geomMaxLength) { // interaction to happen within geometric limit + + // check whether decay or interaction limits this step the + // outcome of decay or interaction MAY be a) new particles in + // secondaries, b) the projectile particle deleted (or + // changed) + + TStackView secondaries(vParticle); + + if (min_distance != distance_max && min_distance != magMaxLength) { + /* + Create SecondaryView object on Stack. The data container + remains untouched and identical, and 'projectil' is identical + to 'vParticle' above this line. However, + projectil.AddSecondaries populate the SecondaryView, which can + then be used afterwards for further processing. Thus: it is + important to use projectle (and not vParticle) for Interaction, + and Decay! + */ + + [[maybe_unused]] auto projectile = secondaries.GetProjectile(); + + if (min_distance == distance_interact) { + interaction(vParticle, projectile); + } else { + assert(min_distance == distance_decay); + decay(vParticle, projectile); + // make sure particle actually did decay if it should have done so + if (secondaries.GetSize() == 1 && + projectile.GetPID() == secondaries.GetNextParticle().GetPID()) + throw std::runtime_error("Cascade::Step: Particle decays into itself!"); + } + + fProcessSequence.DoSecondaries(secondaries); + vParticle.Delete(); // todo: this should be reviewed. Where + // exactly are particles best deleted, and + // where they should NOT be + // deleted... maybe Delete function should + // be "protected" and not accessible to physics + + } else { // step-length limitation within volume + + std::cout << "step-length limitation" << std::endl; + fProcessSequence.DoSecondaries(secondaries); + } + + [[maybe_unused]] auto const assertion = [&] { + auto const* numericalNodeAfterStep = + fEnvironment.GetUniverse()->GetContainingNode(vParticle.GetPosition()); + return numericalNodeAfterStep == currentLogicalNode; + }; + + assert(assertion()); // numerical and logical nodes don't match + } else { // boundary crossing, step is limited by volume boundary + std::cout << "boundary crossing! next node = " << nextVol << std::endl; + vParticle.SetNode(nextVol); + // DoBoundary may delete the particle (or not) + fProcessSequence.DoBoundaryCrossing(vParticle, *currentLogicalNode, *nextVol); + } + } + + auto decay(Particle& particle, + decltype(std::declval<TStackView>().GetProjectile()) projectile) { + std::cout << "decay" << std::endl; + units::si::InverseTimeType const actual_decay_time = + fProcessSequence.GetTotalInverseLifetime(particle); + + random::UniformRealDistribution<units::si::InverseTimeType> uniDist( + actual_decay_time); + const auto sample_process = uniDist(fRNG); + units::si::InverseTimeType inv_decay_count = units::si::InverseTimeType::zero(); + return fProcessSequence.SelectDecay(particle, projectile, sample_process, + inv_decay_count); + } + + auto interaction(Particle& particle, + decltype(std::declval<TStackView>().GetProjectile()) projectile) { + std::cout << "collide" << std::endl; + + units::si::InverseGrammageType const current_inv_length = + fProcessSequence.GetTotalInverseInteractionLength(particle); + + random::UniformRealDistribution<units::si::InverseGrammageType> uniDist( + current_inv_length); + const auto sample_process = uniDist(fRNG); + auto inv_lambda_count = units::si::InverseGrammageType::zero(); + return fProcessSequence.SelectInteraction(particle, projectile, sample_process, + inv_lambda_count); + } + + private: + corsika::environment::Environment<MediumInterface> const& fEnvironment; + TTracking& fTracking; + TProcessList& fProcessSequence; + TStack& fStack; + corsika::random::RNG& fRNG = + corsika::random::RNGManager::GetInstance().GetRandomStream("cascade"); + }; // namespace corsika::cascade + +} // namespace corsika::cascade + +#endif diff --git a/Framework/Utilities/quartic.cpp b/Framework/Utilities/quartic.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f11a8e083e887a3f11ac05dba394f232fdcedd3f --- /dev/null +++ b/Framework/Utilities/quartic.cpp @@ -0,0 +1,152 @@ +/*************************************************************************** + * Copyright (C) 2016 by Саша Миленковић * + * sasa.milenkovic.xyz@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * ( http://www.gnu.org/licenses/gpl-3.0.en.html ) * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include <cmath> +#include "quartic.h" + +//--------------------------------------------------------------------------- +// solve cubic equation x^3 + a*x^2 + b*x + c +// x - array of size 3 +// In case 3 real roots: => x[0], x[1], x[2], return 3 +// 2 real roots: x[0], x[1], return 2 +// 1 real root : x[0], x[1] ± i*x[2], return 1 +unsigned int solveP3(double *x,double a,double b,double c) { + double a2 = a*a; + double q = (a2 - 3*b)/9; + double r = (a*(2*a2-9*b) + 27*c)/54; + double r2 = r*r; + double q3 = q*q*q; + double A,B; + if(r2<q3) + { + double t=r/sqrt(q3); + if( t<-1) t=-1; + if( t> 1) t= 1; + t=acos(t); + a/=3; q=-2*sqrt(q); + x[0]=q*cos(t/3)-a; + x[1]=q*cos((t+M_2PI)/3)-a; + x[2]=q*cos((t-M_2PI)/3)-a; + return 3; + } + else + { + A =-pow(fabs(r)+sqrt(r2-q3),1./3); + if( r<0 ) A=-A; + B = (0==A ? 0 : q/A); + + a/=3; + x[0] =(A+B)-a; + x[1] =-0.5*(A+B)-a; + x[2] = 0.5*sqrt(3.)*(A-B); + if(fabs(x[2])<eps) { x[2]=x[1]; return 2; } + + return 1; + } +} + +//--------------------------------------------------------------------------- +// solve quartic equation x^4 + a*x^3 + b*x^2 + c*x + d +// Attention - this function returns dynamically allocated array. It has to be released afterwards. +DComplex* solve_quartic(double a, double b, double c, double d) +{ + double a3 = -b; + double b3 = a*c -4.*d; + double c3 = -a*a*d - c*c + 4.*b*d; + + // cubic resolvent + // y^3 − b*y^2 + (ac−4d)*y − a^2*d−c^2+4*b*d = 0 + + double x3[3]; + unsigned int iZeroes = solveP3(x3, a3, b3, c3); + + double q1, q2, p1, p2, D, sqD, y; + + y = x3[0]; + // The essence - choosing Y with maximal absolute value. + if(iZeroes != 1) + { + if(fabs(x3[1]) > fabs(y)) y = x3[1]; + if(fabs(x3[2]) > fabs(y)) y = x3[2]; + } + + // h1+h2 = y && h1*h2 = d <=> h^2 -y*h + d = 0 (h === q) + + D = y*y - 4*d; + if(fabs(D) < eps) //in other words - D==0 + { + q1 = q2 = y * 0.5; + // g1+g2 = a && g1+g2 = b-y <=> g^2 - a*g + b-y = 0 (p === g) + D = a*a - 4*(b-y); + if(fabs(D) < eps) //in other words - D==0 + p1 = p2 = a * 0.5; + + else + { + sqD = sqrt(D); + p1 = (a + sqD) * 0.5; + p2 = (a - sqD) * 0.5; + } + } + else + { + sqD = sqrt(D); + q1 = (y + sqD) * 0.5; + q2 = (y - sqD) * 0.5; + // g1+g2 = a && g1*h2 + g2*h1 = c ( && g === p ) Krammer + p1 = (a*q1-c)/(q1-q2); + p2 = (c-a*q2)/(q1-q2); + } + + DComplex* retval = new DComplex[4]; + + // solving quadratic eq. - x^2 + p1*x + q1 = 0 + D = p1*p1 - 4*q1; + if(D < 0.0) + { + retval[0].real( -p1 * 0.5 ); + retval[0].imag( sqrt(-D) * 0.5 ); + retval[1] = std::conj(retval[0]); + } + else + { + sqD = sqrt(D); + retval[0].real( (-p1 + sqD) * 0.5 ); + retval[1].real( (-p1 - sqD) * 0.5 ); + } + + // solving quadratic eq. - x^2 + p2*x + q2 = 0 + D = p2*p2 - 4*q2; + if(D < 0.0) + { + retval[2].real( -p2 * 0.5 ); + retval[2].imag( sqrt(-D) * 0.5 ); + retval[3] = std::conj(retval[2]); + } + else + { + sqD = sqrt(D); + retval[2].real( (-p2 + sqD) * 0.5 ); + retval[3].real( (-p2 - sqD) * 0.5 ); + } + + return retval; +} + diff --git a/Framework/Utilities/quartic.h b/Framework/Utilities/quartic.h new file mode 100644 index 0000000000000000000000000000000000000000..968ebbbd79ca170d8a7ae2ec27737240203b57b5 --- /dev/null +++ b/Framework/Utilities/quartic.h @@ -0,0 +1,69 @@ +/*************************************************************************** + * Copyright (C) 2016 by Саша Миленковић * + * sasa.milenkovic.xyz@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * ( http://www.gnu.org/licenses/gpl-3.0.en.html ) * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef QUARTIC_H_INCLUDED +#define QUARTIC_H_INCLUDED + +#include <complex> + +const double PI = 3.141592653589793238463L; +const double M_2PI = 2*PI; +const double eps=1e-12; + +typedef std::complex<double> DComplex; + +//--------------------------------------------------------------------------- +// useful for testing + inline DComplex polinom_2(DComplex x, double a, double b) + { + //Horner's scheme for x*x + a*x + b + return x * (x + a) + b; + } + +//--------------------------------------------------------------------------- +// useful for testing + inline DComplex polinom_3(DComplex x, double a, double b, double c) + { + //Horner's scheme for x*x*x + a*x*x + b*x + c; + return x * (x * (x + a) + b) + c; + } + +//--------------------------------------------------------------------------- +// useful for testing + inline DComplex polinom_4(DComplex x, double a, double b, double c, double d) + { + //Horner's scheme for x*x*x*x + a*x*x*x + b*x*x + c*x + d; + return x * (x * (x * (x + a) + b) + c) + d; + } + +//--------------------------------------------------------------------------- +// x - array of size 3 +// In case 3 real roots: => x[0], x[1], x[2], return 3 +// 2 real roots: x[0], x[1], return 2 +// 1 real root : x[0], x[1] ± i*x[2], return 1 +unsigned int solveP3(double* x, double a, double b, double c); + +//--------------------------------------------------------------------------- +// solve quartic equation x^4 + a*x^3 + b*x^2 + c*x + d +// Attention - this function returns dynamically allocated array. It has to be released afterwards. +DComplex* solve_quartic(double a, double b, double c, double d); + + +#endif // QUARTIC_H_INCLUDED diff --git a/Processes/TrackingLine/TrackingLine_interpolation.h b/Processes/TrackingLine/TrackingLine_interpolation.h new file mode 100644 index 0000000000000000000000000000000000000000..228855cfe2c0ba93e69f0f1aebc1d890fa5f9442 --- /dev/null +++ b/Processes/TrackingLine/TrackingLine_interpolation.h @@ -0,0 +1,202 @@ +/* + * (c) Copyright 2018 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. + */ + +#ifndef _include_corsika_processes_TrackingLine_h_ +#define _include_corsika_processes_TrackingLine_h_ + +#include <corsika/geometry/Line.h> +#include <corsika/geometry/Plane.h> +#include <corsika/geometry/Sphere.h> +#include <corsika/geometry/Trajectory.h> +#include <corsika/geometry/Vector.h> +#include <corsika/particles/ParticleProperties.h> +#include <corsika/units/PhysicalUnits.h> +#include <corsika/utl/quartic.h> +#include <cmath> +#include <optional> +#include <type_traits> +#include <utility> + +namespace corsika::environment { + template <typename IEnvironmentModel> + class Environment; + template <typename IEnvironmentModel> + class VolumeTreeNode; +} // namespace corsika::environment + +namespace corsika::process { + + namespace tracking_line { + + std::optional<std::pair<corsika::units::si::TimeType, corsika::units::si::TimeType>> + TimeOfIntersection(geometry::Line const&, geometry::Sphere const&); + + corsika::units::si::TimeType TimeOfIntersection(geometry::Line const&, + geometry::Plane const&); + + class TrackingLine { + + public: + TrackingLine(){}; + + template <typename Particle> // was Stack previously, and argument was + // Stack::StackIterator + auto GetTrack(Particle const& p) { + using namespace corsika::units::si; + using namespace corsika::geometry; + geometry::Vector<SpeedType::dimension_type> velocity = + p.GetMomentum() / p.GetEnergy() * corsika::units::constants::c; + + std::complex<double>* solutions = solve_quartic(1, 0, 1, -20); + std::vector<double> tmp; + for(int i = 0; i < 4; i++) { + if(solutions[i].imag() == 0 && solutions[i].real() >= 0) { + tmp.push_back(solutions[i].real()); + } + } + double s = *std::min_element(tmp.begin(),tmp.end()); + std::cout << "s = " << s << std::endl; + std::cout << "x1 = " << (solutions[0].real()>=0. ? " " : "") << solutions[0].real(); if(solutions[0].imag()!=0.0) std::cout << " + i * " << solutions[0].imag(); std::cout << std::endl; + std::cout << "x2 = " << (solutions[1].real()>=0. ? " " : "") << solutions[1].real(); if(solutions[1].imag()!=0.0) std::cout << " - i * " << -solutions[1].imag(); std::cout << std::endl; + std::cout << "x3 = " << (solutions[2].real()>=0. ? " " : "") << solutions[2].real(); if(solutions[2].imag()!=0.0) std::cout << " + i * " << solutions[2].imag(); std::cout << std::endl; + std::cout << "x4 = " << (solutions[3].real()>=0. ? " " : "") << solutions[3].real(); if(solutions[3].imag()!=0.0) std::cout << " - i * " << -solutions[3].imag(); std::cout << std::endl; + + auto const currentPosition = p.GetPosition(); + std::cout << "TrackingLine pid: " << p.GetPID() + << " , E = " << p.GetEnergy() / 1_GeV << " GeV" << std::endl; + std::cout << "TrackingLine pos: " + << currentPosition.GetCoordinates() + // << " [" << p.GetNode()->GetModelProperties().GetName() << "]" + << std::endl; + std::cout << "TrackingLine E: " << p.GetEnergy() / 1_GeV << " GeV" << std::endl; + + // determine velocity after adding magnetic field + auto const* currentLogicalVolumeNode = p.GetNode(); + int chargeNumber; + if(corsika::particles::IsNucleus(p.GetPID())) { + chargeNumber = p.GetNuclearZ(); + } else { + chargeNumber = corsika::particles::GetChargeNumber(p.GetPID()); + } + geometry::Vector<dimensionless_d> const directionBefore = velocity.normalized(); + auto magMaxLength = 1_m/0; + auto directionAfter = directionBefore; + if(chargeNumber != 0) { + auto magneticfield = currentLogicalVolumeNode->GetModelProperties().GetMagneticField(currentPosition); + std::cout << "TrackingLine B: " << magneticfield.GetComponents() / 1_uT << " uT " << std::endl; + geometry::Vector<SpeedType::dimension_type> const velocityVerticalMag = velocity - + velocity.parallelProjectionOnto(magneticfield); + LengthType const gyroradius = p.GetEnergy() * velocityVerticalMag.GetNorm() * 1_V / + (corsika::units::constants::cSquared * abs(chargeNumber) * + magneticfield.GetNorm() * 1_eV); + //steplength should consider more things than just gyroradius + double maxAngle = 0.1; + LengthType const Steplength = 2 * sin(maxAngle * M_PI / 180) * gyroradius; + // First Movement + auto position = currentPosition + directionBefore * Steplength / 2; + // Change of direction by magnetic field at position + magneticfield = currentLogicalVolumeNode->GetModelProperties().GetMagneticField(position); + directionAfter = directionBefore + directionBefore.cross(magneticfield) * chargeNumber * + Steplength * corsika::units::constants::cSquared * 1_eV / + (p.GetEnergy() * velocity.GetNorm() * 1_V); + // Second Movement + position = position + directionAfter * Steplength / 2; + magMaxLength = (position - currentPosition).GetNorm(); + geometry::Vector<dimensionless_d> const direction = (position - currentPosition) / + magMaxLength; + velocity = direction * velocity.GetNorm(); + std::cout << "TrackingLine p: " << (direction * p.GetMomentum().GetNorm()).GetComponents() / 1_GeV + << " GeV " << std::endl; + + } else { + std::cout << "TrackingLine p: " << p.GetMomentum().GetComponents() / 1_GeV + << " GeV " << std::endl; + } + + std::cout << "TrackingLine v: " << velocity.GetComponents() << std::endl; + + geometry::Line line(currentPosition, velocity); + + //auto const* currentLogicalVolumeNode = p.GetNode(); + //~ auto const* currentNumericalVolumeNode = + //~ fEnvironment.GetUniverse()->GetContainingNode(currentPosition); + auto const numericallyInside = + currentLogicalVolumeNode->GetVolume().Contains(currentPosition); + + std::cout << "numericallyInside = " << (numericallyInside ? "true" : "false"); + + auto const& children = currentLogicalVolumeNode->GetChildNodes(); + auto const& excluded = currentLogicalVolumeNode->GetExcludedNodes(); + + std::vector<std::pair<TimeType, decltype(p.GetNode())>> intersections; + + // for entering from outside + auto addIfIntersects = [&](auto const& vtn) { + auto const& volume = vtn.GetVolume(); + auto const& sphere = dynamic_cast<geometry::Sphere const&>( + volume); // for the moment we are a bit bold here and assume + // everything is a sphere, crashes with exception if not + + if (auto opt = TimeOfIntersection(line, sphere); opt.has_value()) { + auto const [t1, t2] = *opt; + std::cout << "intersection times: " << t1 / 1_s << "; " + << t2 / 1_s + // << " " << vtn.GetModelProperties().GetName() + << std::endl; + if (t1.magnitude() > 0) + intersections.emplace_back(t1, &vtn); + else if (t2.magnitude() > 0) + std::cout << "inside other volume" << std::endl; + } + }; + + for (auto const& child : children) { addIfIntersects(*child); } + for (auto const* ex : excluded) { addIfIntersects(*ex); } + + { + auto const& sphere = dynamic_cast<geometry::Sphere const&>( + currentLogicalVolumeNode->GetVolume()); + // for the moment we are a bit bold here and assume + // everything is a sphere, crashes with exception if not + [[maybe_unused]] auto const [t1, t2] = *TimeOfIntersection(line, sphere); + [[maybe_unused]] auto dummy_t1 = t1; + intersections.emplace_back(t2, currentLogicalVolumeNode->GetParent()); + } + + auto const minIter = std::min_element( + intersections.cbegin(), intersections.cend(), + [](auto const& a, auto const& b) { return a.first < b.first; }); + + TimeType min; + + if (minIter == intersections.cend()) { + min = 1_s; // todo: do sth. more reasonable as soon as tracking is able + // to handle the numerics properly + throw std::runtime_error("no intersection with anything!"); + } else { + min = minIter->first; + } + + std::cout << " t-intersect: " + << min + // << " " << minIter->second->GetModelProperties().GetName() + << std::endl; + + return std::make_tuple(geometry::Trajectory<geometry::Line>(line, min), + velocity.norm() * min, minIter->second, magMaxLength, + directionBefore, directionAfter); + } + }; + + } // namespace tracking_line + +} // namespace corsika::process + +#endif