From 8dc6d688f9b86e2d94452d8e8c19e30ad11e1491 Mon Sep 17 00:00:00 2001 From: ralfulrich <ralf.ulrich@kit.edu> Date: Thu, 31 Dec 2020 12:00:08 +0100 Subject: [PATCH] missing files --- corsika/detail/modules/TrackingLine.inl | 59 ++++ corsika/modules/Tracking.hpp | 18 ++ .../tracking/TrackingLeapFrogCurved.hpp | 298 ++++++++++++++++++ .../tracking/TrackingLeapFrogStraight.hpp | 225 +++++++++++++ 4 files changed, 600 insertions(+) create mode 100644 corsika/detail/modules/TrackingLine.inl create mode 100644 corsika/modules/Tracking.hpp create mode 100644 corsika/modules/tracking/TrackingLeapFrogCurved.hpp create mode 100644 corsika/modules/tracking/TrackingLeapFrogStraight.hpp diff --git a/corsika/detail/modules/TrackingLine.inl b/corsika/detail/modules/TrackingLine.inl new file mode 100644 index 000000000..52200c42d --- /dev/null +++ b/corsika/detail/modules/TrackingLine.inl @@ -0,0 +1,59 @@ +/* + * (c) Copyright 2020 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/framework/geometry/Point.hpp> +#include <corsika/framework/geometry/QuantityVector.hpp> +#include <corsika/framework/geometry/Sphere.hpp> +#include <corsika/framework/geometry/Vector.hpp> +#include <corsika/media/Environment.hpp> + +#include <limits> +#include <stdexcept> +#include <utility> + +namespace corsika::tracking_line { + + std::optional<std::pair<TimeType, TimeType>> TimeOfIntersection( + corsika::Line const& line, corsika::Sphere const& sphere) { + auto const delta = line.getStartPoint() - sphere.getCenter(); + auto const v = line.getVelocity(); + auto const vSqNorm = + v.getSquaredNorm(); // todo: get rid of this by having V0 normalized always + auto const R = sphere.getRadius(); + + auto const vDotDelta = v.dot(delta); + auto const discriminant = + vDotDelta * vDotDelta - vSqNorm * (delta.getSquaredNorm() - R * R); + + if (discriminant.magnitude() > 0) { + auto const sqDisc = sqrt(discriminant); + auto const invDenom = 1 / vSqNorm; + return std::make_pair((-vDotDelta - sqDisc) * invDenom, + (-vDotDelta + sqDisc) * invDenom); + } else { + return {}; + } + } + + TimeType getTimeOfIntersection(Line const& vLine, Plane const& vPlane) { + + auto const delta = vPlane.getCenter() - vLine.getStartPoint(); + auto const v = vLine.getVelocity(); + auto const n = vPlane.getNormal(); + auto const c = n.dot(v); + + if (c.magnitude() == 0) { + return std::numeric_limits<TimeType::value_type>::infinity() * 1_s; + } else { + return n.dot(delta) / c; + } + } + +} // namespace corsika::tracking_line diff --git a/corsika/modules/Tracking.hpp b/corsika/modules/Tracking.hpp new file mode 100644 index 000000000..fb12412fb --- /dev/null +++ b/corsika/modules/Tracking.hpp @@ -0,0 +1,18 @@ +/* + * (c) Copyright 2020 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/modules/tracking/TrackingStraight.hpp> +#include <corsika/modules/tracking/TrackingLeapFrogStraight.hpp> // simple leap-frog implementation with two straight lines +#include <corsika/modules/tracking/TrackingLeapFrogCurved.hpp> // // more complete, curved, leap-frog + +/** + * \todo add TrackingCurved, with simple circular trajectories + \todo add Boris algorithm + */ diff --git a/corsika/modules/tracking/TrackingLeapFrogCurved.hpp b/corsika/modules/tracking/TrackingLeapFrogCurved.hpp new file mode 100644 index 000000000..bb75daf62 --- /dev/null +++ b/corsika/modules/tracking/TrackingLeapFrogCurved.hpp @@ -0,0 +1,298 @@ +/* + * (c) Copyright 2020 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/modules/tracking/TrackingStraight.hpp> // for neutral particles +#include <corsika/framework/geometry/Line.hpp> +#include <corsika/framework/geometry/Plane.hpp> +#include <corsika/framework/geometry/Sphere.hpp> +#include <corsika/framework/geometry/Trajectory.hpp> +#include <corsika/framework/geometry/Vector.hpp> +#include <corsika/framework/geometry/Intersections.hpp> +#include <corsika/framework/core/ParticleProperties.hpp> +#include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/framework/utility/QuarticSolver.hpp> +#include <corsika/framework/logging/Logging.hpp> +#include <corsika/modules/tracking/Intersect.hpp> + +#include <type_traits> +#include <utility> + +namespace corsika { + + namespace tracking_leapfrog_curved { + + /** + * \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(TParticle const& particle, LengthType steplength) { + if (particle.getMomentum().norm() == 0_GeV) { + return std::make_tuple(particle.getPosition(), particle.getMomentum() / 1_GeV, + double(0)); + } // charge of the particle + int const chargeNumber = particle.getChargeNumber(); + auto const* currentLogicalVolumeNode = particle.getNode(); + MagneticFieldVector const& magneticfield = + currentLogicalVolumeNode->getModelProperties().getMagneticField( + particle.getPosition()); + VelocityVector velocity = + particle.getMomentum() / particle.getEnergy() * constants::c; + decltype(meter / (second * volt)) k = + chargeNumber * constants::cSquared * 1_eV / + (velocity.getNorm() * particle.getEnergy() * 1_V); + DirectionVector direction = velocity.normalized(); + auto position = particle.getPosition(); // First Movement + // assuming magnetic field does not change during movement + position = + position + direction * steplength / 2; // Change of direction by magnetic field + direction = + direction + direction.cross(magneticfield) * steplength * k; // Second Movement + position = position + direction * steplength / 2; + auto steplength_true = steplength * (1.0 + (double)direction.getNorm()) / 2; + return std::make_tuple(position, direction.normalized(), steplength_true); + } + + /** + * + * The class tracking_leapfrog_curved::Tracking is based on the + * Bachelor thesis of Andre Schmidt (KIT). It implements a + * two-step leap-frog algorithm, but with analytically exact geometric + * intersections between leap-frog steps and geometric volumes + * (spheres, planes). + * + **/ + + class Tracking : public Intersect<Tracking> { + + using Intersect<Tracking>::nextIntersect; + + public: + Tracking() + : straightTracking_{tracking_line::Tracking()} {} + + template <typename TParticle> + auto getTrack(TParticle const& particle) { + VelocityVector const initialVelocity = + particle.getMomentum() / particle.getEnergy() * constants::c; + + auto const position = particle.getPosition(); + CORSIKA_LOG_DEBUG( + "Tracking pid: {}" + " , E = {} GeV", + particle.getPID(), particle.getEnergy() / 1_GeV); + CORSIKA_LOG_DEBUG("Tracking pos: {}", position.getCoordinates()); + CORSIKA_LOG_DEBUG("Tracking E: {} GeV", particle.getEnergy() / 1_GeV); + CORSIKA_LOG_DEBUG("Tracking p: {} GeV", + particle.getMomentum().getComponents() / 1_GeV); + CORSIKA_LOG_DEBUG("Tracking v: {} ", initialVelocity.getComponents()); + + typedef + typename std::remove_reference<decltype(*particle.getNode())>::type node_type; + node_type& volumeNode = *particle.getNode(); + + // for the event of magnetic fields and curved trajectories, we need to limit + // 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 + MagneticFieldVector const& magneticfield = + volumeNode.getModelProperties().getMagneticField(position); + MagneticFluxType const magnitudeB = magneticfield.getNorm(); + 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().getParallelProjectionOnto(magneticfield)) + .getNorm(); + + 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. + CORSIKA_LOG_TRACE("pAlongB_delta is 0_GeV --> parallel"); + return getLinearTrajectory(particle); + } + + LengthType const gyroradius = + (pAlongB_delta * 1_V / + (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.getNorm(); + CORSIKA_LOG_DEBUG("gyroradius {}, steplimit: {} = {}", gyroradius, steplimit, + steplimit_time); + + // traverse the environment volume tree and find next + // intersection + auto [minTime, minNode] = nextIntersect(particle, steplimit_time); + + const auto k = + chargeNumber * constants::cSquared * 1_eV / (particle.getEnergy() * 1_V); + return std::make_tuple( + LeapFrogTrajectory(position, initialVelocity, magneticfield, k, + minTime), // trajectory + minNode); // next volume node + } + + template <typename TParticle, typename TMedium> + static Intersections intersect(const TParticle& particle, const Sphere& sphere, + const TMedium& medium) { + + if (sphere.getRadius() == 1_km * std::numeric_limits<double>::infinity()) { + return Intersections(); + } + + int const chargeNumber = particle.getChargeNumber(); + auto const& position = particle.getPosition(); + MagneticFieldVector const& magneticfield = medium.getMagneticField(position); + + VelocityVector const velocity = + particle.getMomentum() / particle.getEnergy() * constants::c; + DirectionVector const directionBefore = + velocity.normalized(); // determine steplength to next volume + + auto const projectedDirection = directionBefore.cross(magneticfield); + auto const projectedDirectionSqrNorm = projectedDirection.getSquaredNorm(); + bool const isParallel = (projectedDirectionSqrNorm == 0 * square(1_T)); + + if (chargeNumber == 0 || magneticfield.getNorm() == 0_T || isParallel) { + return tracking_line::Tracking::intersect(particle, sphere, medium); + } + + bool const numericallyInside = sphere.isInside(particle.getPosition()); + + const auto absVelocity = velocity.getNorm(); + auto energy = particle.getEnergy(); + auto k = chargeNumber * constants::cSquared * 1_eV / (absVelocity * energy * 1_V); + + 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 * denom); + const double b = directionBefore.dot(position - sphere.getCenter()) * 8 / + (denom * 1_m * 1_m * 1_m); + const double c = ((position - sphere.getCenter()).getSquaredNorm() - + (sphere.getRadius() * sphere.getRadius())) * + 4 / (denom * 1_m * 1_m * 1_m * 1_m); + CORSIKA_LOG_TRACE("denom={}, a={}, b={}, c={}", denom, a, b, c); + std::complex<double>* solutions = quartic_solver::solve_quartic(0, a, b, c); + LengthType d_enter, d_exit; + int first = 0, first_entry = 0, first_exit = 0; + for (int i = 0; i < 4; i++) { + if (solutions[i].imag() == 0) { + LengthType const dist = solutions[i].real() * 1_m; + CORSIKA_LOG_TRACE("Solution (real) for current Volume: {} ", dist); + if (numericallyInside) { + // there must be an entry (negative) and exit (positive) solution + if (dist < -0.0001_m) { // security margin to assure transfer to next + // logical volume + if (first_entry == 0) { + d_enter = dist; + } else { + d_enter = std::max(d_enter, dist); // closest negative to zero (-1e-4) m + } + first_entry++; + + } else { // thus, dist >= -0.0001_m + + if (first_exit == 0) { + d_exit = dist; + } else { + d_exit = std::min(d_exit, dist); // closest positive to zero (-1e-4) m + } + first_exit++; + } + first = int(first_exit > 0) + int(first_entry > 0); + + } else { // thus, numericallyInside == false + + // both physical solutions (entry, exit) must be positive, and as small as + // possible + if (dist < -0.0001_m) { // need small numerical margin, to assure transport + // into next logical volume + continue; + } + if (first == 0) { + d_enter = dist; + } else { + if (dist < d_enter) { + d_exit = d_enter; + d_enter = dist; + } else { + d_exit = dist; + } + } + first++; + } + } // loop over solutions + } + delete[] solutions; + + if (first != 2) { // entry and exit points found + CORSIKA_LOG_DEBUG("no intersection! count={}", first); + return Intersections(); + } + return Intersections(d_enter / absVelocity, d_exit / absVelocity); + } + + template <typename TParticle, typename TBaseNodeType> + static Intersections intersect(const TParticle& particle, + const TBaseNodeType& volumeNode) { + const Sphere* sphere = dynamic_cast<const Sphere*>(&volumeNode.getVolume()); + if (sphere) { + return intersect(particle, *sphere, volumeNode.getModelProperties()); + } + 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) { + + // perform simple linear tracking + auto [straightTrajectory, minNode] = straightTracking_.getTrack(particle); + + // return as leap-frog trajectory + return std::make_tuple( + LeapFrogTrajectory( + straightTrajectory.getLine().getStartPoint(), + straightTrajectory.getLine().getVelocity(), + 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 + +} // namespace corsika diff --git a/corsika/modules/tracking/TrackingLeapFrogStraight.hpp b/corsika/modules/tracking/TrackingLeapFrogStraight.hpp new file mode 100644 index 000000000..0bc38b93d --- /dev/null +++ b/corsika/modules/tracking/TrackingLeapFrogStraight.hpp @@ -0,0 +1,225 @@ +/* + * (c) Copyright 2020 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/framework/geometry/Line.hpp> +#include <corsika/framework/geometry/Plane.hpp> +#include <corsika/framework/geometry/Sphere.hpp> +#include <corsika/framework/geometry/Trajectory.hpp> +#include <corsika/framework/geometry/Vector.hpp> +#include <corsika/framework/core/ParticleProperties.hpp> +#include <corsika/framework/core/PhysicalUnits.hpp> + +#include <corsika/modules/tracking/TrackingStraight.hpp> + +#include <type_traits> +#include <utility> + +namespace corsika { + + namespace tracking_leapfrog_straight { + + /** + * + * The class tracking_leapfrog_straight::Tracking inherits from + * tracking_line::Tracking and adds a (two-step) Leap-Frog + * algorithms with two halve-steps and magnetic deflection. + * + * The two halve steps are implemented as two straight explicit + * `tracking_line::Tracking`s and all geometry intersections are, + * thus, based on those two straight line elements. + * + * As a precaution for numerical instability, the steplength is + * limited to correspond to a straight line distance to the next + * volume intersection. In typical situations this leads to about + * (at least) one full leap-frog step to the next volume boundary. + * + **/ + + 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 const firstFraction = 0.55) + : firstFraction_(firstFraction) {} + + template <typename Particle> + auto getTrack(Particle& particle) { + VelocityVector initialVelocity = + particle.getMomentum() / particle.getEnergy() * constants::c; + + const Point initialPosition = particle.getPosition(); + CORSIKA_LOG_DEBUG( + "TrackingB pid: {}" + " , E = {} GeV", + particle.getPID(), particle.getEnergy() / 1_GeV); + CORSIKA_LOG_DEBUG("TrackingB pos: {}", initialPosition.getCoordinates()); + CORSIKA_LOG_DEBUG("TrackingB E: {} GeV", particle.getEnergy() / 1_GeV); + CORSIKA_LOG_DEBUG("TrackingB p: {} GeV", + particle.getMomentum().getComponents() / 1_GeV); + CORSIKA_LOG_DEBUG("TrackingB v: {} ", initialVelocity.getComponents()); + + typedef decltype(particle.getNode()) node_type; + node_type const volumeNode = particle.getNode(); + + // check if particle is moving at all + auto const absVelocity = initialVelocity.getNorm(); + if (absVelocity * 1_s == 0_m) { + return std::make_tuple( + LineTrajectory(Line(initialPosition, initialVelocity), 0_s), volumeNode); + } + + // charge of the particle, and magnetic field + const int chargeNumber = particle.getChargeNumber(); + auto magneticfield = + volumeNode->getModelProperties().getMagneticField(initialPosition); + const auto magnitudeB = magneticfield.getNorm(); + CORSIKA_LOG_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); + { [[maybe_unused]] auto& initialTrackNextVolume_dum = initialTrackNextVolume; } + const auto initialTrackLength = initialTrack.getLength(1); + + CORSIKA_LOG_DEBUG("initialTrack(0)={}, initialTrack(1)={}, initialTrackLength={}", + initialTrack.getPosition(0).getCoordinates(), + initialTrack.getPosition(1).getCoordinates(), + initialTrackLength); + + // if particle is non-deflectable, we are done: + if (no_deflection) { + CORSIKA_LOG_DEBUG("no deflection. tracking finished"); + return std::make_tuple(initialTrack, initialTrackNextVolume); + } + + HEPMomentumType const pAlongB_delta = + (particle.getMomentum() - + particle.getMomentum().getParallelProjectionOnto(magneticfield)) + .getNorm(); + + 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. + CORSIKA_LOG_TRACE("pAlongB_delta is 0_GeV --> parallel"); + return std::make_tuple(initialTrack, initialTrackNextVolume); + } + + LengthType const gyroradius = + (pAlongB_delta * 1_V / + (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; + CORSIKA_LOG_DEBUG("gyroradius {}, Steplimit: {}", gyroradius, steplimit); + + // calculate first halve step for "steplimit" + auto const initialMomentum = particle.getMomentum(); + auto const absMomentum = initialMomentum.getNorm(); + DirectionVector const direction = initialVelocity.normalized(); + + // avoid any intersections within first halve steplength + LengthType const firstHalveSteplength = + std::min(steplimit, initialTrackLength * firstFraction_); + + CORSIKA_LOG_DEBUG( + "first halve step length {}, steplimit={}, initialTrackLength={}", + firstHalveSteplength, steplimit, initialTrackLength); + // perform the first halve-step + const Point position_mid = initialPosition + direction * firstHalveSteplength; + const auto k = + chargeNumber * constants::c * 1_eV / (particle.getMomentum().getNorm() * 1_V); + const auto new_direction = + direction + direction.cross(magneticfield) * firstHalveSteplength * 2 * k; + const auto new_direction_norm = new_direction.getNorm(); // by design this is >1 + CORSIKA_LOG_DEBUG( + "position_mid={}, new_direction={}, (new_direction_norm)={}, deflection={}", + position_mid.getCoordinates(), new_direction.getComponents(), + new_direction_norm, + acos(std::min(1.0, direction.dot(new_direction) / new_direction_norm)) * 180 / + M_PI); + + // check, where the second halve-step direction has geometric intersections + particle.setPosition(position_mid); + particle.setMomentum(new_direction * absMomentum); + const auto [finalTrack, finalTrackNextVolume] = + tracking_line::Tracking::getTrack(particle); + particle.setPosition(initialPosition); // this is not nice... + particle.setMomentum(initialMomentum); // this is not nice... + + LengthType const finalTrackLength = finalTrack.getLength(1); + LengthType const secondLeapFrogLength = firstHalveSteplength * new_direction_norm; + + // check if volume transition is obvious, OR + // for numerical reasons, particles slighly bend "away" from a + // volume boundary have a very hard time to cross the border, + // thus, if secondLeapFrogLength is just slighly shorter (1e-4m) than + // finalTrackLength we better just [extend the + // secondLeapFrogLength slightly and] force the volume + // crossing: + bool const switch_volume = finalTrackLength - 0.0001_m <= secondLeapFrogLength; + LengthType const secondHalveStepLength = + std::min(secondLeapFrogLength, finalTrackLength); + + CORSIKA_LOG_DEBUG( + "finalTrack(0)={}, finalTrack(1)={}, finalTrackLength={}, " + "secondLeapFrogLength={}, secondHalveStepLength={}, " + "secondLeapFrogLength-finalTrackLength={}, " + "secondHalveStepLength-finalTrackLength={}, " + "nextVol={}, transition={}", + finalTrack.getPosition(0).getCoordinates(), + finalTrack.getPosition(1).getCoordinates(), finalTrackLength, + secondLeapFrogLength, secondHalveStepLength, + secondLeapFrogLength - finalTrackLength, + secondHalveStepLength - finalTrackLength, fmt::ptr(finalTrackNextVolume), + switch_volume); + + // perform the second halve-step + auto const new_direction_normalized = new_direction.normalized(); + const Point finalPosition = + position_mid + new_direction_normalized * secondHalveStepLength; + + const LengthType totalStep = firstHalveSteplength + secondHalveStepLength; + const auto delta_pos = finalPosition - initialPosition; + const auto distance = delta_pos.getNorm(); + + return std::make_tuple( + LineTrajectory(Line(initialPosition, + (distance == 0_m ? initialVelocity + : delta_pos.normalized() * absVelocity)), + distance / absVelocity, // straight distance + totalStep / absVelocity, // bend distance + initialVelocity, + new_direction_normalized * absVelocity), // trajectory + (switch_volume ? finalTrackNextVolume : volumeNode)); + } + + protected: + double firstFraction_; + }; + + } // namespace tracking_leapfrog_straight + +} // namespace corsika -- GitLab