From 5d07a85c6c9744ce1ad5afaa82f9723cb7890d05 Mon Sep 17 00:00:00 2001 From: ralfulrich <ralf.ulrich@kit.edu> Date: Tue, 20 Apr 2021 14:44:27 +0200 Subject: [PATCH] solver update --- conanfile.txt | 2 +- corsika/detail/framework/core/Cascade.inl | 61 +++- corsika/detail/framework/geometry/Helix.inl | 2 +- .../framework/process/ProcessSequence.inl | 14 +- .../process/SwitchProcessSequence.inl | 36 +-- .../detail/framework/utility/CubicSolver.inl | 299 ++++++++++++++++++ .../detail/framework/utility/LinearSolver.inl | 36 +++ .../framework/utility/QuadraticSolver.inl | 81 +++++ .../framework/utility/QuarticSolver.inl | 265 +++++++++------- corsika/detail/modules/ObservationPlane.inl | 9 +- .../tracking/TrackingLeapFrogCurved.inl | 273 ++++++++-------- .../tracking/TrackingLeapFrogStraight.inl | 25 +- .../modules/tracking/TrackingStraight.inl | 1 + corsika/detail/modules/urqmd/UrQMD.inl | 4 +- .../detail/stack/NuclearStackExtension.inl | 8 + corsika/framework/core/Cascade.hpp | 5 +- .../framework/geometry/LeapFrogTrajectory.hpp | 4 +- corsika/framework/utility/CubicSolver.hpp | 88 ++++++ corsika/framework/utility/LinearSolver.hpp | 24 ++ corsika/framework/utility/QuadraticSolver.hpp | 27 ++ corsika/framework/utility/QuarticSolver.hpp | 69 ++-- corsika/modules/ObservationPlane.hpp | 17 +- corsika/stack/NuclearStackExtension.hpp | 4 + corsika/stack/VectorStack.hpp | 6 + examples/vertical_EAS.cpp | 42 ++- tests/framework/CMakeLists.txt | 1 + tests/framework/testCOMBoost.cpp | 3 - tests/framework/testCascade.cpp | 1 - tests/framework/testClassTimer.cpp | 1 - tests/framework/testCombinedStack.cpp | 2 - tests/framework/testCorsikaFenv.cpp | 1 - tests/framework/testFourVector.cpp | 1 - tests/framework/testFunctionTimer.cpp | 1 - tests/framework/testHelix.cpp | 1 - tests/framework/testInteractionCounter.cpp | 1 - tests/framework/testLogging.cpp | 2 +- tests/framework/testNullModel.cpp | 1 - tests/framework/testParticles.cpp | 1 - tests/framework/testRandom.cpp | 2 - tests/framework/testSaveBoostHistogram.cpp | 1 - tests/framework/testSecondaryView.cpp | 1 - tests/framework/testSolver.cpp | 278 ++++++++++++++++ tests/framework/testStackInterface.cpp | 1 - tests/framework/testUnits.cpp | 1 - tests/modules/testCONEX.cpp | 2 - tests/modules/testExecTime.cpp | 1 - tests/modules/testNullModel.cpp | 1 - tests/modules/testOnShellCheck.cpp | 1 - tests/modules/testQGSJetII.cpp | 11 +- tests/modules/testSibyll.cpp | 2 - tests/modules/testStackInspector.cpp | 1 - tests/modules/testTracking.cpp | 14 +- tests/modules/testUrQMD.cpp | 19 -- 53 files changed, 1340 insertions(+), 415 deletions(-) create mode 100644 corsika/detail/framework/utility/CubicSolver.inl create mode 100644 corsika/detail/framework/utility/LinearSolver.inl create mode 100644 corsika/detail/framework/utility/QuadraticSolver.inl create mode 100644 corsika/framework/utility/CubicSolver.hpp create mode 100644 corsika/framework/utility/LinearSolver.hpp create mode 100644 corsika/framework/utility/QuadraticSolver.hpp create mode 100644 tests/framework/testSolver.cpp diff --git a/conanfile.txt b/conanfile.txt index 85c1238b4..b4471ff3d 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -1,5 +1,5 @@ [requires] -spdlog/1.8.0 +spdlog/1.8.5 catch2/2.13.3 eigen/3.3.8 boost/1.74.0 diff --git a/corsika/detail/framework/core/Cascade.inl b/corsika/detail/framework/core/Cascade.inl index 28306c590..f30ec982f 100644 --- a/corsika/detail/framework/core/Cascade.inl +++ b/corsika/detail/framework/core/Cascade.inl @@ -60,7 +60,7 @@ namespace corsika { setNodes(); auto vParticle = stack_.getNextParticle(); TStackView secondaries(vParticle); - interaction(secondaries); + interaction(secondaries, sequence_.getInverseInteractionLength(vParticle)); sequence_.doSecondaries(secondaries); vParticle.erase(); // primary particle is done } @@ -243,15 +243,15 @@ namespace corsika { [[maybe_unused]] auto projectile = secondaries.getProjectile(); if (distance_interact < distance_decay) { - interaction(secondaries); + interaction(secondaries, total_inv_lambda); } else { - decay(secondaries); - // make sure particle actually did decay if it should have done so - // \todo this should go to a validation code and not be included here - if (secondaries.getSize() == 1 && - projectile.getPID() == secondaries.getNextParticle().getPID()) - throw std::runtime_error(fmt::format("Particle {} decays into itself!", - get_name(projectile.getPID()))); + if (decay(secondaries, total_inv_lifetime) == ProcessReturn::Decayed) { + if (secondaries.getSize() == 1 && + projectile.getPID() == secondaries.getNextParticle().getPID()) { + throw std::runtime_error(fmt::format("Particle {} decays into itself!", + get_name(projectile.getPID()))); + } + } } sequence_.doSecondaries(secondaries); @@ -261,16 +261,29 @@ namespace corsika { template <typename TTracking, typename TProcessList, typename TStack, typename TStackView> inline ProcessReturn Cascade<TTracking, TProcessList, TStack, TStackView>::decay( - TStackView& view) { + TStackView& view, InverseTimeType initial_inv_decay_time) { CORSIKA_LOG_DEBUG("decay"); + +#ifdef DEBUG InverseTimeType const actual_decay_time = sequence_.getInverseLifetime(view.parent()); + if (actual_decay_time > initial_inv_decay_time) { + CORSIKA_LOG_WARN( + "Decay time decreased during step! This leads to un-physical step length. " + "initial_decay_time={}, actual_decay_time={}", + 1 / initial_inv_decay_time, 1 / actual_decay_time); + } +#endif - UniformRealDistribution<InverseTimeType> uniDist(actual_decay_time); + // one option is that decay_time is now larger (less + // probability for decay) than it was before the step, thus, + // no decay might actually occur and is allowed + + UniformRealDistribution<InverseTimeType> uniDist(initial_inv_decay_time); const auto sample_process = uniDist(rng_); auto const returnCode = sequence_.selectDecay(view, sample_process); if (returnCode != ProcessReturn::Decayed) { - CORSIKA_LOG_WARN("Particle did not decay!"); + CORSIKA_LOG_DEBUG("Particle did not decay!"); } setEventType(view, history::EventType::Decay); return returnCode; @@ -279,18 +292,32 @@ namespace corsika { template <typename TTracking, typename TProcessList, typename TStack, typename TStackView> inline ProcessReturn Cascade<TTracking, TProcessList, TStack, TStackView>::interaction( - TStackView& view) { + TStackView& view, InverseGrammageType initial_inv_int_length) { + CORSIKA_LOG_DEBUG("collide"); - InverseGrammageType const current_inv_length = - sequence_.getInverseInteractionLength(view.parent()); +#ifdef DEBUG + InverseGrammageType const actual_inv_length = sequence_.getInverseInteractionLength( + view.parent()); // 1/lambda_int after step, -dE/dX etc. + + if (actual_inv_length > initial_inv_int_length) { + CORSIKA_LOG_WARN( + "Interaction length decreased during step! This leads to un-physical step " + "length. " + "initial_inv_int_length={}, actual_inv_length={}", + 1 / initial_inv_int_length, 1 / actual_inv_length); + } +#endif - UniformRealDistribution<InverseGrammageType> uniDist(current_inv_length); + // one option is that interaction_length is now larger (less + // probability for collision) than it was before the step, thus, + // no interaction might actually occur and is allowed + UniformRealDistribution<InverseGrammageType> uniDist(initial_inv_int_length); const auto sample_process = uniDist(rng_); auto const returnCode = sequence_.selectInteraction(view, sample_process); if (returnCode != ProcessReturn::Interacted) { - CORSIKA_LOG_WARN("Particle did not interace!"); + CORSIKA_LOG_DEBUG("Particle did not interact!"); } setEventType(view, history::EventType::Interaction); return returnCode; diff --git a/corsika/detail/framework/geometry/Helix.inl b/corsika/detail/framework/geometry/Helix.inl index 03c0fd85d..5a029d8e2 100644 --- a/corsika/detail/framework/geometry/Helix.inl +++ b/corsika/detail/framework/geometry/Helix.inl @@ -19,7 +19,7 @@ namespace corsika { inline Point Helix::getPosition(TimeType const t) const { return r0_ + vPar_ * t + - (vPerp_ * (std::cos(omegaC_ * t) - 1) + uPerp_ * std::sin(omegaC_ * t)) / + (vPerp_ * (std::cos(omegaC_ * t) - 1) + uPerp_ * std::sin(omegaC_ * t)) / omegaC_; } diff --git a/corsika/detail/framework/process/ProcessSequence.inl b/corsika/detail/framework/process/ProcessSequence.inl index 6c9bd7892..7b9ec0dfb 100644 --- a/corsika/detail/framework/process/ProcessSequence.inl +++ b/corsika/detail/framework/process/ProcessSequence.inl @@ -221,7 +221,7 @@ namespace corsika { IndexProcess2>::doStack(TStack& stack) { if constexpr (is_process_v<process1_type>) { // to protect from further compiler // errors if process1_type is invalid - if constexpr (std::is_base_of_v<StackProcess<process1_type>, process1_type> || + if constexpr (is_stack_process_v<process1_type> || (process1_type::is_process_sequence && !process1_type::is_switch_process_sequence)) { @@ -237,7 +237,7 @@ namespace corsika { } if constexpr (is_process_v<process2_type>) { // to protect from further compiler // errors if process2_type is invalid - if constexpr (std::is_base_of_v<StackProcess<process2_type>, process2_type> || + if constexpr (is_stack_process_v<process2_type> || (process2_type::is_process_sequence && !process2_type::is_switch_process_sequence)) { @@ -316,14 +316,14 @@ namespace corsika { if constexpr (is_process_v<process1_type>) { // to protect from further compiler // errors if process1_type is invalid - if constexpr (std::is_base_of_v<InteractionProcess<process1_type>, process1_type> || + if constexpr (is_interaction_process_v<process1_type> || process1_type::is_process_sequence) { tot += A_.getInverseInteractionLength(particle); } } if constexpr (is_process_v<process2_type>) { // to protect from further compiler // errors if process2_type is invalid - if constexpr (std::is_base_of_v<InteractionProcess<process2_type>, process2_type> || + if constexpr (is_interaction_process_v<process2_type> || process2_type::is_process_sequence) { tot += B_.getInverseInteractionLength(particle); } @@ -351,8 +351,7 @@ namespace corsika { // if A_ did succeed, stop routine. Not checking other static branch B_. if (ret != ProcessReturn::Ok) { return ret; } } else if constexpr (is_process_v<process1_type> && - std::is_base_of_v<InteractionProcess<process1_type>, - process1_type>) { + is_interaction_process_v<process1_type>) { // if this is not a ContinuousProcess --> evaluate probability lambda_inv_sum += A_.getInverseInteractionLength(view.parent()); // check if we should execute THIS process and then EXIT @@ -377,8 +376,7 @@ namespace corsika { // if B_ is a process sequence --> check inside return B_.selectInteraction(view, lambda_inv_select, lambda_inv_sum); } else if constexpr (is_process_v<process2_type> && - std::is_base_of_v<InteractionProcess<process2_type>, - process2_type>) { + is_interaction_process_v<process2_type>) { // if this is not a ContinuousProcess --> evaluate probability lambda_inv_sum += B_.getInverseInteractionLength(view.parent()); // soon as SecondaryView::parent() is migrated! diff --git a/corsika/detail/framework/process/SwitchProcessSequence.inl b/corsika/detail/framework/process/SwitchProcessSequence.inl index 2f87ade6d..634395f55 100644 --- a/corsika/detail/framework/process/SwitchProcessSequence.inl +++ b/corsika/detail/framework/process/SwitchProcessSequence.inl @@ -36,13 +36,11 @@ namespace corsika { typename TParticle::node_type const& from, typename TParticle::node_type const& to) { if (select_(particle)) { - if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process1_type>, - process1_type> || + if constexpr (is_boundary_process_v<process1_type> || process1_type::is_process_sequence) { // interface checking on TSequence - if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process1_type>, - process1_type>) { + if constexpr (is_boundary_process_v<process1_type>) { static_assert( has_method_doBoundaryCrossing_v<TSequence, ProcessReturn, TParticle&>, @@ -56,13 +54,11 @@ namespace corsika { } } else { - if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process2_type>, - process2_type> || + if constexpr (is_boundary_process_v<process2_type> || process2_type::is_process_sequence) { // interface checking on USequence - if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process2_type>, - process2_type>) { + if constexpr (is_boundary_process_v<process2_type>) { static_assert( has_method_doBoundaryCrossing_v<USequence, ProcessReturn, TParticle>, @@ -136,7 +132,7 @@ namespace corsika { IndexProcess2>::doSecondaries(TSecondaries& vS) { const auto& particle = vS.parent(); if (select_(particle)) { - if constexpr (std::is_base_of_v<SecondariesProcess<process1_type>, process1_type> || + if constexpr (is_secondaries_process_v<process1_type> || process1_type::is_process_sequence) { // interface checking on TSequence @@ -150,7 +146,7 @@ namespace corsika { A_.doSecondaries(vS); } } else { - if constexpr (std::is_base_of_v<SecondariesProcess<process2_type>, process2_type> || + if constexpr (is_secondaries_process_v<process2_type> || process2_type::is_process_sequence) { // interface checking on USequence @@ -219,14 +215,14 @@ namespace corsika { IndexProcess2>::getInverseInteractionLength(TParticle&& particle) { if (select_(particle)) { - if constexpr (std::is_base_of_v<InteractionProcess<process1_type>, process1_type> || + if constexpr (is_interaction_process_v<process1_type> || process1_type::is_process_sequence) { return A_.getInverseInteractionLength(particle); } } else { - if constexpr (std::is_base_of_v<InteractionProcess<process2_type>, process2_type> || + if constexpr (is_interaction_process_v<process2_type> || process2_type::is_process_sequence) { return B_.getInverseInteractionLength(particle); } @@ -249,8 +245,7 @@ namespace corsika { A_.selectInteraction(view, lambda_inv_select, lambda_inv_sum); // if A_ did succeed, stop routine. Not checking other static branch B_. if (ret != ProcessReturn::Ok) { return ret; } - } else if constexpr (std::is_base_of_v<InteractionProcess<process1_type>, - process1_type>) { + } else if constexpr (is_interaction_process_v<process1_type>) { // if this is not a ContinuousProcess --> evaluate probability lambda_inv_sum += A_.getInverseInteractionLength(view.parent()); // check if we should execute THIS process and then EXIT @@ -272,8 +267,7 @@ namespace corsika { if constexpr (process2_type::is_process_sequence) { // if B_ is a process sequence --> check inside return B_.selectInteraction(view, lambda_inv_select, lambda_inv_sum); - } else if constexpr (std::is_base_of_v<InteractionProcess<process2_type>, - process2_type>) { + } else if constexpr (is_interaction_process_v<process2_type>) { // if this is not a ContinuousProcess --> evaluate probability lambda_inv_sum += B_.getInverseInteractionLength(view.parent()); // check if we should execute THIS process and then EXIT @@ -301,14 +295,14 @@ namespace corsika { IndexProcess2>::getInverseLifetime(TParticle&& particle) { if (select_(particle)) { - if constexpr (std::is_base_of_v<DecayProcess<process1_type>, process1_type> || + if constexpr (is_decay_process_v<process1_type> || process1_type::is_process_sequence) { return A_.getInverseLifetime(particle); } } else { - if constexpr (std::is_base_of_v<DecayProcess<process2_type>, process2_type> || + if constexpr (is_decay_process_v<process2_type> || process2_type::is_process_sequence) { return B_.getInverseLifetime(particle); } @@ -331,8 +325,7 @@ namespace corsika { ProcessReturn const ret = A_.selectDecay(view, decay_inv_select, decay_inv_sum); // if A_ did succeed, stop routine here (not checking other static branch B_) if (ret != ProcessReturn::Ok) { return ret; } - } else if constexpr (std::is_base_of_v<DecayProcess<process1_type>, - process1_type>) { + } else if constexpr (is_decay_process_v<process1_type>) { // if this is not a ContinuousProcess --> evaluate probability decay_inv_sum += A_.getInverseLifetime(view.parent()); // check if we should execute THIS process and then EXIT @@ -355,8 +348,7 @@ namespace corsika { if constexpr (process2_type::is_process_sequence) { // if B_ is a process sequence --> check inside return B_.selectDecay(view, decay_inv_select, decay_inv_sum); - } else if constexpr (std::is_base_of_v<DecayProcess<process2_type>, - process2_type>) { + } else if constexpr (is_decay_process_v<process2_type>) { // if this is not a ContinuousProcess --> evaluate probability decay_inv_sum += B_.getInverseLifetime(view.parent()); // check if we should execute THIS process and then EXIT diff --git a/corsika/detail/framework/utility/CubicSolver.inl b/corsika/detail/framework/utility/CubicSolver.inl new file mode 100644 index 000000000..efeb48c34 --- /dev/null +++ b/corsika/detail/framework/utility/CubicSolver.inl @@ -0,0 +1,299 @@ +/* + * (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/utility/CubicSolver.hpp> +#include <cmath> + +namespace corsika { + + namespace andre { + + //--------------------------------------------------------------------------- + // solve cubic equation x^3 + a*x^2 + b*x + c = 0 + // 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 + inline std::vector<double> solveP3(long double A, long double B, long double C, + long double D, double const epsilon) { + + if (std::abs(A) < epsilon) { return solve_quadratic_real(B, C, epsilon); } + + long double a = B / A; + long double b = C / A; + long double c = D / A; + + long double a2 = a * a; + long double q = (a2 - 3 * b) / 9; + long double r = (a * (2 * a2 - 9 * b) + 27 * c) / 54; + long double r2 = r * r; + long double q3 = q * q * q; + + // disc = q**3 + r**2 + // w:e = r*r exactly + long double w = r * r; + long double e = std::fma(r, r, -w); + // s:t = q*q exactly + long double s = -q * q; + long double t = std::fma(-q, q, -s); + // s:t * q + w:e = s*q + w + t*q +e = s*q+w + u:v + e = f + u:v + e + long double f = std::fma(s, q, w); + long double u = t * q; + long double v = std::fma(t, q, -u); + // error-free sum f+u. See Knuth, TAOCP vol. 2 + long double uf = u + f; + long double au = uf - u; + long double ab = uf - au; + au = f - au; + ab = u - ab; + // sum all terms into final result + long double const disc = -(((e + uf) + au) + ab) + v; + + CORSIKA_LOG_TRACE("solveP3 disc={} r2-r3={}", disc, r2 - q3); + + if (disc >= 0) { + long double t = r / std::sqrt(q3); + if (t < -1) t = -1; + if (t > 1) t = 1; + t = std::acos(t); + a /= 3; + q = -2 * std::sqrt(q); + return {double(q * std::cos(t / 3) - a), + double(q * std::cos((t + 2 * M_PI) / 3) - a), + double(q * std::cos((t - 2 * M_PI) / 3) - a)}; + } else { + long double A = -std::pow(std::fabs(r) + std::sqrt(-disc), 1. / 3); + if (r < 0) A = -A; + long double B = (0 == A ? 0 : q / A); + + a /= 3; + long double test = 0.5 * std::sqrt(3.) * (A - B); + if (std::fabs(test) < epsilon) { + return {double((A + B) - 1), double(-0.5 * (A + B) - a)}; + } + + return {double((A + B) - a)}; + } + } + } // namespace andre + + inline std::vector<double> solve_cubic_depressed_disciminant_real( + long double p, long double q, long double const disc, double const epsilon) { + + CORSIKA_LOG_TRACE("p={}, q={}, disc={}", p, q, disc); + + if (std::abs(disc) < epsilon) { // disc==0 multiple roots ! + if (std::abs(p) < epsilon) { // tripple root + return {0}; + } + // double root, single root + CORSIKA_LOG_TRACE("cubic double root"); + return {double(-3 * q / (2 * p)), double(3 * q / p)}; + } + + if (std::abs(p) < epsilon) { // p==0 --> x^3 + q = 0 + return {double(std::cbrt(-q))}; + } + + if (disc > 0) { // three real roots + CORSIKA_LOG_TRACE("cubic three roots"); + long double const p_third = p / 3; + long double const sqrt_minus_p_third = std::sqrt(-p_third); + + long double const arg = std::acos(q / (2 * p_third) / sqrt_minus_p_third) / 3; + + long double constexpr pi = M_PI; + return {double(2 * sqrt_minus_p_third * std::cos(arg)), + double(2 * sqrt_minus_p_third * std::cos(arg - 2 * pi / 3)), + double(2 * sqrt_minus_p_third * std::cos(arg - 4 * pi / 3))}; + } + + // thus disc < 0 -> one real root + if (p < 0) { + CORSIKA_LOG_TRACE("cubic cosh"); + long double const abs_q = std::abs(q); + long double const p_third = p / 3; + long double const sqrt_minus_p_third = std::sqrt(-p_third); + CORSIKA_LOG_TRACE("sqrt_minus_p_third={}, arcosh({})={}", sqrt_minus_p_third, + -abs_q / (2 * p_third) / sqrt_minus_p_third, + std::acosh(-abs_q / (2 * p_third) / sqrt_minus_p_third)); + CORSIKA_LOG_TRACE( + "complex: {}", + -2 * abs_q / q * sqrt_minus_p_third * + std::cosh(std::acosh(-abs_q / (2 * p_third * sqrt_minus_p_third)) / 3)); + double const z = + -2 * abs_q / q * sqrt_minus_p_third * + std::cosh(std::acosh(-abs_q / (2 * p_third * sqrt_minus_p_third)) / 3); + return {z}; + } else { // p > 0 + CORSIKA_LOG_TRACE("cubic sinh"); + long double const p_third = p / 3; + long double const sqrt_p_third = std::sqrt(p_third); + return {double(-2 * sqrt_p_third * + std::sinh(std::asinh(q / (2 * p_third * sqrt_p_third)) / 3))}; + } + } + + inline std::vector<double> solve_cubic_depressed_real(long double p, long double q, + double const epsilon) { + + // thanks! + // https://math.stackexchange.com/questions/2434354/numerically-stable-scheme-for-the-3-real-roots-of-a-cubic + // long double const disc = -(4 * p * p * p + 27 * q * q); + // disc = (p/3)**3 + (q/2)**2 + long double p_third = p / 3; + long double q_half = q / 2; + // w:e = (q/2)*(q/2) exactly + long double w = q_half * q_half; + long double e = std::fma(q_half, q_half, -w); + // s:t = (p/3)*(p/3) exactly + long double s = p_third * p_third; + long double t = std::fma(p_third, p_third, -s); + // s:t * p + w:e = s*p + w + t*p +e = s*p+w + u:v + e = f + u:v + e + long double f = std::fma(s, p_third, w); + long double u = t * p_third; + long double v = std::fma(t, p_third, -u); + // error-free sum f+u. See Knuth, TAOCP vol. 2 + long double a = u + f; + long double b = a - u; + long double c = a - b; + b = f - b; + c = u - c; + // sum all terms into final result + long double const disc = -(((e + a) + b) + c) + v; + return solve_cubic_depressed_disciminant_real(p, q, disc, epsilon); + } + + /** + Analytical approach. Not very stable in all conditions. + */ + inline std::vector<double> solve_cubic_real_analytic(long double a, long double b, + long double c, long double d, + double const epsilon) { + + CORSIKA_LOG_TRACE("cubic: a={:f}, b={:f}, c={:f}, d={:f}, epsilon={} {} {}", a, b, c, + d, epsilon, (std::abs(a - 1) < epsilon), (std::abs(b) < epsilon)); + + if (std::abs(a) < epsilon) { // this is just a quadratic + return solve_quadratic_real(b, c, d, epsilon); + } + + if ((std::abs(a - 1) < epsilon) && + (std::abs(b) < epsilon)) { // this is a depressed cubic + return solve_cubic_depressed_real(c, d, epsilon); + } + + // p = (3ac - b^2) / (3a^2) = 3 * ( c/(3*a) - b^2/(9*a^2) ) + long double b_over_a = b / a; + long double const p_third = std::fma(-b_over_a, b_over_a / 9, c / (a * 3)); + + // p = std::fma(a * 3, c, -b2) / (3 * a2); + // q = (2*b^3 - 9*abc + 27*a^2*d) / (27a^3) = 2 * ( b^3/(27*a^3) - bc/(6*a^2) + + // d/(2*a) ) + long double const q_half_term1 = std::fma(b_over_a, b_over_a / 27, -c / (a * 6)); + long double const q_half = std::fma(b_over_a, q_half_term1, d / (a * 2)); + + std::vector<double> zs = solve_cubic_depressed_real(3 * p_third, 2 * q_half, epsilon); + CORSIKA_LOG_TRACE("cubic: solve_depressed={}, b/3a={}", fmt::join(zs, ", "), + b / (a * 3)); + for (auto& z : zs) { + z -= b / (a * 3); + double const b1 = z + b / a; + double const b0 = b1 * z + c / a; + std::vector<double> quad_check = solve_quadratic_real(1, b1, b0, epsilon); + CORSIKA_LOG_TRACE("quad_check=[{}], f(z)={}", fmt::join(quad_check, ", "), + std::pow(z, 3) * a + std::pow(z, 2) * b + std::pow(z, 1) * c + d); + } + CORSIKA_LOG_TRACE("cubic: solve_cubic_real returns={}", fmt::join(zs, ", ")); + return zs; + } + + inline long double cubic_function(long double x, long double a, long double b, + long double c, long double d) { + return std::pow(x, 3) * a + std::pow(x, 2) * b + x * c + d; + } + + inline long double cubic_function_dfdx(long double x, long double a, long double b, + long double c) { + return std::pow(x, 2) * a * 3 + x * b * 2 + c; + } + + inline long double cubic_function_d2fd2x(long double x, long double a, long double b) { + return x * a * 6 + b * 2; + } + + /** + Iterative approach. + */ + + inline std::vector<double> solve_cubic_real(long double a, long double b, long double c, + long double d, double const epsilon) { + + CORSIKA_LOG_TRACE("cubic_2: a={:f}, b={:f}, c={:f}, d={:f}, epsilon={} {} {}", a, b, + c, d, epsilon, (std::abs(a - 1) < epsilon), + (std::abs(b) < epsilon)); + + if (std::abs(a) < epsilon) { // this is just a quadratic + return solve_quadratic_real(b, c, d, epsilon); + } + + long double const dist = std::fma(b / a, b / a, -3 * c / a); + long double const xinfl = -b / (a * 3); + + long double x1 = xinfl; + long double f_x1 = cubic_function(xinfl, a, b, c, d); + + CORSIKA_LOG_TRACE("dist={} xinfl={} f_x1={} {} {}", dist, xinfl, f_x1, + cubic_function(xinfl - 2 / 3 * std::sqrt(dist), a, b, c, d), + cubic_function(xinfl + 2 / 3 * std::sqrt(dist), a, b, c, d)); + + if (std::abs(f_x1) > epsilon) { + if (std::abs(dist) < epsilon) { + x1 = xinfl - std::cbrt(f_x1); + } else if (dist > 0) { + if (f_x1 > 0) + x1 = xinfl - 2 / 3 * std::sqrt(dist); + else + x1 = xinfl + 2 / 3 * std::sqrt(dist); + } + + int niter = 0; + const int maxiter = 100; + do { + long double const f_prime_x1 = cubic_function_dfdx(x1, a, b, c); + long double const f_prime2_x1 = cubic_function_d2fd2x(x1, a, b); + // if (potential) saddle point... avoid + if (std::abs(f_prime_x1) < epsilon) { + x1 -= std::cbrt(f_x1); + } else { + x1 -= f_x1 * f_prime_x1 / (std::pow(f_prime_x1, 2) - 0.5 * f_x1 * f_prime2_x1); + } + f_x1 = cubic_function(x1, a, b, c, d); + CORSIKA_LOG_TRACE("niter={} x1={} f_x1={} f_prime={} f_prime2={} eps={}", niter, + x1, f_x1, f_prime_x1, f_prime2_x1, epsilon); + } while ((++niter < maxiter) && (std::abs(f_x1) > epsilon)); + + CORSIKA_LOG_TRACE("niter={}", niter); + } + + CORSIKA_LOG_TRACE("x1={} f_x1={}", x1, f_x1); + + double const b1 = x1 + b / a; + double const b0 = b1 * x1 + c / a; + std::vector<double> quad_check = solve_quadratic_real(1, b1, b0, epsilon); + CORSIKA_LOG_TRACE("quad_check=[{}], f(z)={}", fmt::join(quad_check, ", "), + cubic_function(x1, a, b, c, d)); + + quad_check.push_back(x1); + CORSIKA_LOG_TRACE("cubic: solve_cubic_real returns={}", fmt::join(quad_check, ", ")); + return quad_check; + } + +} // namespace corsika diff --git a/corsika/detail/framework/utility/LinearSolver.inl b/corsika/detail/framework/utility/LinearSolver.inl new file mode 100644 index 000000000..45ed7a517 --- /dev/null +++ b/corsika/detail/framework/utility/LinearSolver.inl @@ -0,0 +1,36 @@ +/* + * (c) Copyright 2021 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 <vector> +#include <cmath> +#include <complex> + +namespace corsika { + + inline std::vector<double> solve_linear_real(double a, double b, double const epsilon) { + + if (std::abs(a) < epsilon) { + return {}; // no (b!=0), or infinite number (b==0) of solutions.... + } + + return {-b / a}; + } + + inline std::vector<std::complex<double>> solve_linear(double a, double b, + double const epsilon) { + + if (std::abs(a) < epsilon) { + return {}; // no (b!=0), or infinite number (b==0) of solutions.... + } + + return {{-b / a, 0}}; + } + +} // namespace corsika diff --git a/corsika/detail/framework/utility/QuadraticSolver.inl b/corsika/detail/framework/utility/QuadraticSolver.inl new file mode 100644 index 000000000..d3b3f8671 --- /dev/null +++ b/corsika/detail/framework/utility/QuadraticSolver.inl @@ -0,0 +1,81 @@ +/* + * (c) Copyright 2021 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. + */ + +namespace corsika { + + inline std::vector<std::complex<double>> solve_quadratic(long double a, long double b, + long double c, + double const epsilon) { + if (std::abs(a) < epsilon) { return solve_linear(b, c, epsilon); } + + if (std::abs(c) < epsilon) { + std::vector<std::complex<double>> lin_result = solve_linear(a, b, epsilon); + lin_result.push_back({0.}); + return lin_result; + } + + long double const radicant = std::pow(b, 2) - a * c * 4; + + if (radicant < -epsilon) { // two complex solutions + double const rpart = -b / 2 * a; + double const ipart = std::sqrt(-radicant); + return {{rpart, ipart}, {rpart, -ipart}}; + } + + if (radicant < epsilon) { // just one real solution + return {{double(-b / 2 * a), 0}}; + } + + // two real solutions, use Citardauq formula and actively avoid cancellation in the + // denominator + + const long double x1 = + 2 * c / (b > 0 ? -b - std::sqrt(radicant) : -b + std::sqrt(radicant)); + + return {{double(x1), 0}, {double(c / (a * x1)), 0}}; + } + + inline std::vector<double> solve_quadratic_real(long double a, long double b, + long double c, double const epsilon) { + + CORSIKA_LOG_TRACE("quadratic: a={} b={} c={}", a, b, c); + + if (std::abs(a) < epsilon) { return solve_linear_real(b, c, epsilon); } + if (std::abs(c) < epsilon) { + std::vector<double> lin_result = solve_linear_real(a, b, epsilon); + lin_result.push_back(0.); + return lin_result; + } + + // long double const radicant = std::pow(b, 2) - a * c * 4; + // Thanks! + // https://math.stackexchange.com/questions/2434354/numerically-stable-scheme-for-the-3-real-roots-of-a-cubic + long double w = a * 4 * c; + long double e = std::fma(a * 4, c, -w); + long double f = std::fma(b, b, -w); + long double radicant = f + e; + + CORSIKA_LOG_TRACE(" radicant={} {} ", radicant, b * b - a * c * 4); + + if (std::abs(radicant) < epsilon) { // just one real solution + return {double(-b / (2 * a))}; + } + + if (radicant < 0) { return {}; } + + // two real solutions, use Citardauq formula and actively avoid cancellation in the + // denominator + + long double const x1 = + c * 2 / (b > 0 ? -b - std::sqrt(radicant) : -b + std::sqrt(radicant)); + + CORSIKA_LOG_TRACE("quadratic x1={} x2={}", double(x1), double(c / (a * x1))); + + return {double(x1), double(c / (a * x1))}; + } +} // namespace corsika diff --git a/corsika/detail/framework/utility/QuarticSolver.inl b/corsika/detail/framework/utility/QuarticSolver.inl index a82abf7ac..2bed4396c 100644 --- a/corsika/detail/framework/utility/QuarticSolver.inl +++ b/corsika/detail/framework/utility/QuarticSolver.inl @@ -8,128 +8,179 @@ #pragma once +#include <corsika/framework/utility/CubicSolver.hpp> #include <cmath> -namespace corsika::quartic_solver { - - //--------------------------------------------------------------------------- - // solve cubic equation x^3 + a*x^2 + b*x + c = 0 - // 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 - inline 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; +namespace corsika { + + namespace andre { + + inline std::vector<double> solve_quartic_real(long double a, long double b, + long double c, long double d, + long double e, double const epsilon) { + + if (std::abs(a) < epsilon) { return solve_cubic_real(b, c, d, e, epsilon); } + + b /= a; + c /= a; + d /= a; + e /= a; + + long double a3 = -c; + long double b3 = b * d - 4. * e; + long double c3 = -b * b * e - d * d + 4. * c * e; + + // cubic resolvent + // y^3 − b*y^2 + (ac−4d)*y − a^2*d−c^2+4*b*d = 0 + + std::vector<double> x3 = solve_cubic_real(1, a3, b3, c3, epsilon); + long double y = x3[0]; // there is always at least one solution + // The essence - choosing Y with maximal absolute value. + if (x3.size() == 3) { + if (fabs(x3[1]) > fabs(y)) y = x3[1]; + if (fabs(x3[2]) > fabs(y)) y = x3[2]; + } + + long double q1, q2, p1, p2; + // h1+h2 = y && h1*h2 = e <=> h^2 -y*h + e = 0 (h === q) + + long double Det = y * y - 4 * e; + CORSIKA_LOG_TRACE("Det={}", Det); + if (fabs(Det) < epsilon) // in other words - D==0 + { + q1 = q2 = y * 0.5; + // g1+g2 = b && g1+g2 = c-y <=> g^2 - b*g + c-y = 0 (p === g) + Det = b * b - 4 * (c - y); + if (fabs(Det) < epsilon) { // in other words - D==0 + p1 = p2 = b * 0.5; + } else { + if (Det < 0) return {}; + long double sqDet = sqrt(Det); + p1 = (b + sqDet) * 0.5; + p2 = (b - sqDet) * 0.5; + } + } else { + if (Det < 0) return {}; + long double sqDet = sqrt(Det); + q1 = (y + sqDet) * 0.5; + q2 = (y - sqDet) * 0.5; + // g1+g2 = b && g1*h2 + g2*h1 = c ( && g === p ) Krammer + p1 = (b * q1 - d) / (q1 - q2); + p2 = (d - b * q2) / (q1 - q2); } - return 1; + // solving quadratic eqs. x^2 + p1*x + q1 = 0 + // x^2 + p2*x + q2 = 0 + { + long double Det1 = p1 * p1 - 4 * q1; + long double Det2 = p2 * p2 - 4 * q2; + CORSIKA_LOG_TRACE("Det1={} Det2={}", Det1, Det2); + if (Det1 >= 0 && Det2 >= 0) { + long double sqDet1 = sqrt(Det1); + long double sqDet2 = sqrt(Det2); + // return {double(-p1 + sqDet1) * 0.5, double(-p1 - sqDet1) * 0.5, double(-p2 + + // sqDet2) * 0.5, double(-p2 - sqDet2) * 0.5}; + CORSIKA_LOG_TRACE("check1 {} {} {} {}", double(-p1 + sqDet1) * 0.5, + double(-p1 - sqDet1) * 0.5, double(-p2 + sqDet2) * 0.5, + double(-p2 - sqDet2) * 0.5); + } + if (Det1 >= 0) { + long double sqDet1 = sqrt(Det1); + // return {double(-p1 + sqDet1) * 0.5, double(-p1 - sqDet1) * 0.5}; + CORSIKA_LOG_TRACE("check2 {} {} ", double(-p1 + sqDet1) * 0.5, + double(-p1 - sqDet1) * 0.5); + } + if (Det2 >= 0) { + long double sqDet2 = sqrt(Det2); + // return {double(-p2 + sqDet2) * 0.5, double(-p2 - sqDet2) * 0.5}; + CORSIKA_LOG_TRACE("check3 {} {}", double(-p1 + sqDet2) * 0.5, + double(-p1 - sqDet2) * 0.5); + } + } + + std::vector<double> quad1 = solve_quadratic_real(1, p1, q1); + std::vector<double> quad2 = solve_quadratic_real(1, p2, q2); + if (quad2.size() > 0) { + for (auto val : quad2) quad1.push_back(val); + } + return quad1; } - } + } // namespace andre - //--------------------------------------------------------------------------- - // 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. - inline 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; + inline std::vector<double> solve_quartic_depressed_real(long double p, long double q, + long double r, + double const epsilon) { - // cubic resolvent - // y^3 − b*y^2 + (ac−4d)*y − a^2*d−c^2+4*b*d = 0 + CORSIKA_LOG_TRACE("quartic-depressed: p={:f}, q={:f}, r={:f}, epsilon={}", p, q, r, + epsilon); - double x3[3]; - unsigned int iZeroes = solveP3(x3, a3, b3, c3); + long double const p2 = std::pow(p, 2); + long double const q2 = std::pow(q, 2); - double q1, q2, p1, p2, D, sqD, y; + std::vector<double> const resolve_cubic = + solve_cubic_real(1, p, p2 / 4 - r, -q2 / 8, epsilon); - 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]; - } + CORSIKA_LOG_TRACE("resolve_cubic: N={}, m=[{}]", resolve_cubic.size(), + fmt::join(resolve_cubic, ", ")); - // 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); + if (!resolve_cubic.size()) return {}; + + long double m = 0; + for (auto const& v : resolve_cubic) { + CORSIKA_LOG_TRACE("check pol3(v)={}", (std::pow(v, 3) + std::pow(v, 2) * p + + v * (p2 / 4 - r) - q2 / 8)); + if (std::abs(v) > epsilon && std::abs(v) > m) { m = v; } } + CORSIKA_LOG_TRACE("check m={}", m); + if (m == 0) { return {{0}}; } + + CORSIKA_LOG_TRACE("check m={}", m); + + long double const quad_term1 = p / 2 + m; + long double const quad_term2 = std::sqrt(2 * m); + long double const quad_term3 = q / (2 * quad_term2); - 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); + std::vector<double> z_quad1 = + solve_quadratic_real(1, quad_term2, quad_term1 - quad_term3, epsilon); + std::vector<double> z_quad2 = + solve_quadratic_real(1, -quad_term2, quad_term1 + quad_term3, epsilon); + for (auto const& z : z_quad2) z_quad1.push_back(z); + return z_quad1; + } + + inline std::vector<double> solve_quartic_real(long double a, long double b, + long double c, long double d, + long double e, double const epsilon) { + + CORSIKA_LOG_TRACE("quartic: a={:f}, b={:f}, c={:f}, d={:f}, e={:f}, epsilon={}", a, b, + c, d, e, epsilon); + + if (std::abs(a) < epsilon) { // this is just a quadratic + return solve_cubic_real(b, c, d, e, epsilon); } - // 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); + if ((std::abs(a - 1) < epsilon) && + (std::abs(b) < epsilon)) { // this is a depressed quartic + return solve_quartic_depressed_real(c, d, e, epsilon); } - return retval; + long double const b2 = std::pow(b, 2); + long double const b3 = std::pow(b, 3); + long double const b4 = std::pow(b, 4); + long double const a2 = std::pow(a, 2); + long double const a3 = std::pow(a, 3); + long double const a4 = std::pow(a, 4); + + long double const p = (c * a * 8 - b2 * 3) / (a4 * 8); + long double const q = (b3 - b * c * a * 4 + d * a2 * 8) / (a4 * 8); + long double const r = + (-b4 * 3 + e * a3 * 256 - b * d * a2 * 64 + b2 * c * a * 16) / (a4 * 256); + + std::vector<double> zs = solve_quartic_depressed_real(p, q, r, epsilon); + CORSIKA_LOG_TRACE("quartic: solve_depressed={}, b/4a={}", fmt::join(zs, ", "), + b / (4 * a)); + for (auto& z : zs) { z -= b / (4 * a); } + CORSIKA_LOG_TRACE("quartic: solve_quartic_real returns={}", fmt::join(zs, ", ")); + return zs; } - -} // namespace corsika::quartic_solver \ No newline at end of file +} // namespace corsika diff --git a/corsika/detail/modules/ObservationPlane.inl b/corsika/detail/modules/ObservationPlane.inl index c4da50e94..e1fe6a504 100644 --- a/corsika/detail/modules/ObservationPlane.inl +++ b/corsika/detail/modules/ObservationPlane.inl @@ -45,6 +45,8 @@ namespace corsika { << ' ' << displacement.dot(xAxis_) / 1_m << ' ' << displacement.dot(yAxis_) / 1_m << '\n'; + CORSIKA_LOG_TRACE("Particle detected absorbed={}", deleteOnHit_); + if (deleteOnHit_) { count_ground_++; energy_ground_ += energy; @@ -58,13 +60,14 @@ namespace corsika { corsika::setup::Stack::particle_type const& particle, corsika::setup::Trajectory const& trajectory) { + CORSIKA_LOG_TRACE("particle={}, pos={}, dir={}, plane={}", particle.asString(), + particle.getPosition(), particle.getDirection(), plane_.asString()); + Intersections const intersection = setup::Tracking::intersect<corsika::setup::Stack::particle_type>(particle, plane_); TimeType const timeOfIntersection = intersection.getEntry(); - CORSIKA_LOG_TRACE("particle={}, pos={}, dir={}, plane={}, timeOfIntersection={}", - particle.asString(), particle.getPosition(), - particle.getDirection(), plane_.asString(), timeOfIntersection); + CORSIKA_LOG_TRACE("timeOfIntersection={}", timeOfIntersection); if (timeOfIntersection < TimeType::zero()) { return std::numeric_limits<double>::infinity() * 1_m; } diff --git a/corsika/detail/modules/tracking/TrackingLeapFrogCurved.inl b/corsika/detail/modules/tracking/TrackingLeapFrogCurved.inl index 5770fe439..f780175b5 100644 --- a/corsika/detail/modules/tracking/TrackingLeapFrogCurved.inl +++ b/corsika/detail/modules/tracking/TrackingLeapFrogCurved.inl @@ -34,15 +34,14 @@ namespace corsika { return std::make_tuple(particle.getPosition(), particle.getMomentum() / 1_GeV, double(0)); } // charge of the particle - int const chargeNumber = particle.getChargeNumber(); + ElectricChargeType const charge = particle.getCharge(); auto const* currentLogicalVolumeNode = particle.getNode(); MagneticFieldVector const& magneticfield = currentLogicalVolumeNode->getModelProperties().getMagneticField( particle.getPosition()); - VelocityVector velocity = - particle.getMomentum() / particle.getEnergy() * constants::c; + VelocityVector velocity = particle.getVelocity(); decltype(meter / (second * volt)) k = - chargeNumber * constants::cSquared * 1_eV / + charge * constants::cSquared * 1_eV / (velocity.getNorm() * particle.getEnergy() * 1_V); DirectionVector direction = velocity.normalized(); auto position = particle.getPosition(); // First Movement @@ -58,8 +57,7 @@ namespace corsika { template <typename TParticle> inline auto Tracking::getTrack(TParticle const& particle) { - VelocityVector const initialVelocity = - particle.getMomentum() / particle.getEnergy() * constants::c; + VelocityVector const initialVelocity = particle.getVelocity(); auto const position = particle.getPosition(); CORSIKA_LOG_DEBUG( @@ -74,7 +72,7 @@ namespace corsika { typedef typename std::remove_reference<decltype(*particle.getNode())>::type node_type; - node_type& volumeNode = *particle.getNode(); + node_type const& 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 @@ -83,25 +81,25 @@ namespace corsika { 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; + ElectricChargeType const charge = particle.getCharge(); + bool const no_deflection = (charge == 0 * constants::e) || magnitudeB == 0_T; if (no_deflection) { return getLinearTrajectory(particle); } - HEPMomentumType const pAlongB_delta = + HEPMomentumType const p_perp = (particle.getMomentum() - particle.getMomentum().getParallelProjectionOnto(magneticfield)) .getNorm(); - if (pAlongB_delta == 0_GeV) { + if (p_perp < 1_eV) { // 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"); + CORSIKA_LOG_TRACE("p_perp is 0_GeV --> parallel"); return getLinearTrajectory(particle); } - LengthType const gyroradius = - (pAlongB_delta * 1_V / (constants::c * abs(chargeNumber) * magnitudeB * 1_eV)); + LengthType const gyroradius = (convert_HEP_to_SI<MassType::dimension_type>(p_perp) * + constants::c / (abs(charge) * magnitudeB)); double const maxRadians = 0.01; LengthType const steplimit = 2 * cos(maxRadians) * sin(maxRadians) * gyroradius; @@ -113,8 +111,15 @@ namespace corsika { // intersection auto [minTime, minNode] = nextIntersect(particle, steplimit_time); - auto const k = - chargeNumber * constants::cSquared * 1_eV / (particle.getEnergy() * 1_V); + auto const p_norm = + constants::c * convert_HEP_to_SI<MassType::dimension_type>( + particle.getMomentum().getNorm()); // kg *m /s + // k = q/|p| + decltype(1 / (tesla * second)) const k = + charge / p_norm * + initialVelocity.getNorm(); // since we use steps in time and not length + // units: C * s / m / kg * m/s = 1 / (T*m) * m/s = 1/(T s) + return std::make_tuple( LeapFrogTrajectory(position, initialVelocity, magneticfield, k, minTime), // trajectory @@ -129,99 +134,104 @@ namespace corsika { return Intersections(); } - int const chargeNumber = particle.getChargeNumber(); + ElectricChargeType const charge = particle.getCharge(); auto const& position = particle.getPosition(); auto const* currentLogicalVolumeNode = particle.getNode(); MagneticFieldVector const& magneticfield = currentLogicalVolumeNode->getModelProperties().getMagneticField(position); - VelocityVector const velocity = - particle.getMomentum() / particle.getEnergy() * constants::c; - DirectionVector const directionBefore = - velocity.normalized(); // determine steplength to next volume + VelocityVector const velocity = particle.getVelocity(); + DirectionVector const directionBefore = velocity.normalized(); 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) { + if ((charge == 0 * constants::e) || magneticfield.getNorm() == 0_T || isParallel) { return tracking_line::Tracking::intersect<TParticle>(particle, sphere); } bool const numericallyInside = sphere.contains(particle.getPosition()); CORSIKA_LOG_TRACE("numericallyInside={}", numericallyInside); - auto const absVelocity = velocity.getNorm(); - auto const energy = particle.getEnergy(); + SpeedType const absVelocity = velocity.getNorm(); + auto const p_norm = + constants::c * convert_HEP_to_SI<MassType::dimension_type>( + particle.getMomentum().getNorm()); // km * m /s // this is: k = q/|p| - auto const k = - chargeNumber * constants::cSquared * 1_eV / (absVelocity * energy * 1_V); + auto const k = charge / p_norm; - auto const direction_B_perp = directionBefore.cross(magneticfield); - auto const denom = 4. / (direction_B_perp.getSquaredNorm() * k * k); + MagneticFieldVector const direction_x_B = directionBefore.cross(magneticfield); + auto const denom = 4. / (direction_x_B.getSquaredNorm() * k * k); Vector<length_d> const deltaPos = position - sphere.getCenter(); - double const b = (direction_B_perp.dot(deltaPos) * k + 1) * denom / (1_m * 1_m); + double const b = (direction_x_B.dot(deltaPos) * k + 1) * denom / (1_m * 1_m); double const c = directionBefore.dot(deltaPos) * 2 * denom / (1_m * 1_m * 1_m); LengthType const deltaPosLength = deltaPos.getNorm(); double const d = (deltaPosLength + sphere.getRadius()) * (deltaPosLength - sphere.getRadius()) * denom / (1_m * 1_m * 1_m * 1_m); CORSIKA_LOG_TRACE("denom={}, b={}, c={}, d={}", denom, b, c, d); - std::complex<double> const* solutions = quartic_solver::solve_quartic(0, b, c, d); + std::vector<double> solutions = andre::solve_quartic_real(1, 0, b, c, d); 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++; + for (auto solution : solutions) { + LengthType const dist = solution * 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 + // (even if dist suggest marginal + // entry already, which we + // classify as numerical artifact) + if (first_entry == 0) { + d_enter = dist; + } else { + d_enter = std::max(d_enter, dist); // closest negative to zero >1e-4 m } - first = int(first_exit > 0) + int(first_entry > 0); + first_entry++; - } else { // thus, numericallyInside == false + } else { // thus, dist > +0.0001_m - // 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_exit == 0) { + d_exit = dist; + } else { + d_exit = std::min(d_exit, dist); // closest positive to zero >1e-4 m } - if (first == 0) { + 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. We consider + // begin marginally already inside + // next volume (besides + // numericallyInside=false) as numerical glitch. + // into next logical volume + continue; + } + if (first == 0) { + d_enter = dist; + } else { + if (dist < d_enter) { + d_exit = d_enter; d_enter = dist; } else { - if (dist < d_enter) { - d_exit = d_enter; - d_enter = dist; - } else { - d_exit = dist; - } + d_exit = dist; } - first++; } - } // loop over solutions - } - delete[] solutions; + first++; + } + } // loop over solutions - if (first != 2) { // entry and exit points found - CORSIKA_LOG_DEBUG("no intersection! count={}", first); + if (first == 0) { // entry and exit points found + CORSIKA_LOG_DEBUG( + "no intersections found: count={}, first_entry={}, first_exit={}", first, + first_entry, first_exit); return Intersections(); } return Intersections(d_enter / absVelocity, d_exit / absVelocity); @@ -231,79 +241,80 @@ namespace corsika { inline Intersections Tracking::intersect(TParticle const& particle, Plane const& plane) { - int chargeNumber; - if (is_nucleus(particle.getPID())) { - chargeNumber = particle.getNuclearZ(); - } else { - chargeNumber = get_charge_number(particle.getPID()); - } - auto const* currentLogicalVolumeNode = particle.getNode(); - VelocityVector const velocity = - particle.getMomentum() / particle.getEnergy() * constants::c; - auto const absVelocity = velocity.getNorm(); - DirectionVector const direction = - velocity.normalized(); // determine steplength to next volume - Point const position = particle.getPosition(); - - auto const magneticfield = - currentLogicalVolumeNode->getModelProperties().getMagneticField(position); + CORSIKA_LOG_TRACE("intersection particle with plane"); + + ElectricChargeType const charge = particle.getCharge(); - if (chargeNumber != 0 && abs(plane.getNormal().dot(velocity.cross(magneticfield))) > - 1e-6_T * 1_m / 1_s) { + if (charge != 0 * constants::e) { auto const* currentLogicalVolumeNode = particle.getNode(); + VelocityVector const velocity = particle.getVelocity(); + auto const absVelocity = velocity.getNorm(); + DirectionVector const direction = velocity.normalized(); + Point const position = particle.getPosition(); + auto const magneticfield = currentLogicalVolumeNode->getModelProperties().getMagneticField(position); - auto const k = - chargeNumber * (constants::c * 1_eV / 1_V) / particle.getMomentum().getNorm(); - auto const direction_B_perp = direction.cross(magneticfield); - auto const denom = plane.getNormal().dot(direction_B_perp) * k; - auto const sqrtArg = - direction.dot(plane.getNormal()) * direction.dot(plane.getNormal()) - - (plane.getNormal().dot(position - plane.getCenter()) * denom * 2); + // solve: denom x^2 + p x + q =0 for x = delta-l + + auto const direction_x_B = direction.cross(magneticfield); + double const denom = charge * + plane.getNormal().dot(direction_x_B) // unit: C*T = kg/s + / 1_kg * 1_s; + + CORSIKA_LOG_TRACE("denom={}", denom); + + auto const p_norm = + constants::c * convert_HEP_to_SI<MassType::dimension_type>( + particle.getMomentum().getNorm()); // unit: kg * m/s + + double const p = (2 * p_norm * direction.dot(plane.getNormal())) // unit: kg*m/s + / (1_m * 1_kg) * 1_s; + double const q = + (2 * p_norm * + plane.getNormal().dot(position - plane.getCenter())) // unit: kg*m/s *m + / (1_m * 1_m * 1_kg) * 1_s; + + std::vector<double> deltaLs = solve_quadratic_real(denom, p, q); - if (sqrtArg < 0) { + if (deltaLs.size() == 0) { return Intersections(std::numeric_limits<double>::infinity() * 1_s); } - double const sqrSqrtArg = sqrt(sqrtArg); - auto const norm_projected = - direction.dot(plane.getNormal()) / direction.getNorm(); - LengthType const MaxStepLength1 = (sqrSqrtArg - norm_projected) / denom; - LengthType const MaxStepLength2 = (-sqrSqrtArg - norm_projected) / denom; - CORSIKA_LOG_TRACE("MaxStepLength1={}, MaxStepLength2={}", MaxStepLength1, - MaxStepLength2); + // select smallest but positive solution + bool first = true; + LengthType maxStepLength = 0_m; + for (auto& deltaL : deltaLs) { + if (deltaL < 0) continue; + if (first) { + first = false; + maxStepLength = deltaL * meter; + } else if (maxStepLength > deltaL * meter) { + maxStepLength = deltaL * meter; + } + } - // check: both intersections in past - if (MaxStepLength1 <= 0_m && MaxStepLength2 <= 0_m) { + // check: both intersections in past, or no valid intersection + if (first) { return Intersections(std::numeric_limits<double>::infinity() * 1_s); - - // check: next intersection is MaxStepLength2 - } else if (MaxStepLength1 <= 0_m || MaxStepLength2 < MaxStepLength1) { - CORSIKA_LOG_TRACE(" steplength to obs plane 2: {} ", MaxStepLength2); - return Intersections( - MaxStepLength2 * - (direction + direction_B_perp * MaxStepLength2 * k / 2).getNorm() / - absVelocity); - - // check: next intersections is MaxStepLength1 - } else if (MaxStepLength2 <= 0_m || MaxStepLength1 < MaxStepLength2) { - CORSIKA_LOG_TRACE(" steplength to obs plane 2: {} ", MaxStepLength1); - return Intersections( - MaxStepLength1 * - (direction + direction_B_perp * MaxStepLength1 * k / 2).getNorm() / - absVelocity); } - CORSIKA_LOG_WARN( - "Particle wasn't tracked with curved trajectory -> straight (is this an " - "ERROR?)"); + CORSIKA_LOG_TRACE("maxStepLength={}", maxStepLength); + + // with final length correction + auto const corr = + (direction + direction_x_B * maxStepLength * charge / (p_norm * 2)) + .getNorm() / + absVelocity; // unit: s/m + + return Intersections(maxStepLength * corr); // unit: s - } // end if curved-tracking + } // no charge - CORSIKA_LOG_TRACE("straight tracking with chargeNumber={}, B={}", chargeNumber, - magneticfield); + CORSIKA_LOG_TRACE("(plane) straight tracking with charge={}, B={}", charge, + particle.getNode()->getModelProperties().getMagneticField( + particle.getPosition())); return tracking_line::Tracking::intersect(particle, plane); } @@ -330,7 +341,7 @@ namespace corsika { straightTrajectory.getLine().getVelocity(), MagneticFieldVector(particle.getPosition().getCoordinateSystem(), 0_T, 0_T, 0_T), - square(0_m) / (square(1_s) * 1_V), + 0 * square(meter) / (square(second) * volt), straightTrajectory.getDuration()), // trajectory minNode); // next volume node } diff --git a/corsika/detail/modules/tracking/TrackingLeapFrogStraight.inl b/corsika/detail/modules/tracking/TrackingLeapFrogStraight.inl index 61ad3793d..77cef175d 100644 --- a/corsika/detail/modules/tracking/TrackingLeapFrogStraight.inl +++ b/corsika/detail/modules/tracking/TrackingLeapFrogStraight.inl @@ -50,14 +50,13 @@ namespace corsika { } // charge of the particle, and magnetic field - const int chargeNumber = particle.getChargeNumber(); + auto const charge = particle.getCharge(); auto magneticfield = volumeNode->getModelProperties().getMagneticField(initialPosition); auto const 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; + CORSIKA_LOG_DEBUG("field={} uT, charge={}, magnitudeB={} uT", + magneticfield.getComponents() / 1_uT, charge, magnitudeB / 1_T); + bool const no_deflection = (charge == 0 * constants::e) || magnitudeB == 0_T; // check, where the first halve-step direction has geometric intersections auto const [initialTrack, initialTrackNextVolume] = @@ -75,20 +74,20 @@ namespace corsika { return std::make_tuple(initialTrack, initialTrackNextVolume); } - HEPMomentumType const pAlongB_delta = + HEPMomentumType const p_perp = (particle.getMomentum() - particle.getMomentum().getParallelProjectionOnto(magneticfield)) .getNorm(); - if (pAlongB_delta == 0_GeV) { + if (p_perp == 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"); + CORSIKA_LOG_TRACE("p_perp is 0_GeV --> parallel"); return std::make_tuple(initialTrack, initialTrackNextVolume); } - LengthType const gyroradius = - (pAlongB_delta * 1_V / (constants::c * abs(chargeNumber) * magnitudeB * 1_eV)); + LengthType const gyroradius = (convert_HEP_to_SI<MassType::dimension_type>(p_perp) * + constants::c / (abs(charge) * magnitudeB)); // we need to limit maximum step-length since we absolutely // need to follow strongly curved trajectories segment-wise, @@ -111,9 +110,9 @@ namespace corsika { firstHalveSteplength, steplimit, initialTrackLength); // perform the first halve-step Point const position_mid = initialPosition + direction * firstHalveSteplength; - auto const k = - chargeNumber * (constants::c * 1_eV / 1_V) / particle.getMomentum().getNorm(); - auto const new_direction = + auto const k = charge / (constants::c * convert_HEP_to_SI<MassType::dimension_type>( + particle.getMomentum().getNorm())); + DirectionVector const new_direction = direction + direction.cross(magneticfield) * firstHalveSteplength * 2 * k; auto const new_direction_norm = new_direction.getNorm(); // by design this is >1 CORSIKA_LOG_DEBUG( diff --git a/corsika/detail/modules/tracking/TrackingStraight.inl b/corsika/detail/modules/tracking/TrackingStraight.inl index 9a4659250..36e097560 100644 --- a/corsika/detail/modules/tracking/TrackingStraight.inl +++ b/corsika/detail/modules/tracking/TrackingStraight.inl @@ -20,6 +20,7 @@ #include <type_traits> #include <utility> +#include <cmath> namespace corsika::tracking_line { diff --git a/corsika/detail/modules/urqmd/UrQMD.inl b/corsika/detail/modules/urqmd/UrQMD.inl index ee29642e0..f251c60ba 100644 --- a/corsika/detail/modules/urqmd/UrQMD.inl +++ b/corsika/detail/modules/urqmd/UrQMD.inl @@ -268,10 +268,8 @@ namespace corsika::urqmd { 2 * ::urqmd::options_.CTParam[30 - 1]; ::urqmd::rsys_.ebeam = (projectileEnergyLab - projectile.getMass()) * (1 / 1_GeV); - if (projectileCode == Code::K0Long) { + if (projectileCode == Code::K0Long || projectileCode == Code::K0Short) { projectileCode = booleanDist_(RNG_) ? Code::K0 : Code::K0Bar; - } else if (projectileCode == Code::K0Short) { - throw std::runtime_error("K0Short should not interact"); } auto const [ityp, iso3] = convertToUrQMD(projectileCode); diff --git a/corsika/detail/stack/NuclearStackExtension.inl b/corsika/detail/stack/NuclearStackExtension.inl index 728647ca0..5dab28ab2 100644 --- a/corsika/detail/stack/NuclearStackExtension.inl +++ b/corsika/detail/stack/NuclearStackExtension.inl @@ -113,6 +113,14 @@ namespace corsika::nuclear_stack { return super_type::getMass(); } + template <template <typename> class InnerParticleInterface, + typename StackIteratorInterface> + inline ElectricChargeType NuclearParticleInterface< + InnerParticleInterface, StackIteratorInterface>::getCharge() const { + if (super_type::getPID() == Code::Nucleus) return getNuclearZ() * constants::e; + return super_type::getCharge(); + } + template <template <typename> class InnerParticleInterface, typename StackIteratorInterface> inline int16_t NuclearParticleInterface< diff --git a/corsika/framework/core/Cascade.hpp b/corsika/framework/core/Cascade.hpp index 7ee12186a..b9ee5361b 100644 --- a/corsika/framework/core/Cascade.hpp +++ b/corsika/framework/core/Cascade.hpp @@ -130,8 +130,9 @@ namespace corsika { */ void step(Particle& vParticle); - ProcessReturn decay(TStackView& view); - ProcessReturn interaction(TStackView& view); + ProcessReturn decay(TStackView& view, InverseTimeType initial_inv_decay_time); + ProcessReturn interaction(TStackView& view, + InverseGrammageType initial_inv_int_length); void setEventType(TStackView& view, history::EventType); // data members diff --git a/corsika/framework/geometry/LeapFrogTrajectory.hpp b/corsika/framework/geometry/LeapFrogTrajectory.hpp index 6b67bf634..11e3534a1 100644 --- a/corsika/framework/geometry/LeapFrogTrajectory.hpp +++ b/corsika/framework/geometry/LeapFrogTrajectory.hpp @@ -39,7 +39,7 @@ namespace corsika { LeapFrogTrajectory(Point const& pos, VelocityVector const& initialVelocity, MagneticFieldVector const& Bfield, - decltype(square(meter) / (square(second) * volt)) const k, + decltype(1 / (tesla * second)) const k, TimeType const timeStep) // leap-from total length : initialPosition_(pos) , initialVelocity_(initialVelocity) @@ -74,7 +74,7 @@ namespace corsika { VelocityVector initialVelocity_; DirectionVector initialDirection_; MagneticFieldVector magneticfield_; - decltype(square(meter) / (square(second) * volt)) k_; + decltype(1 / (tesla * second)) k_; TimeType timeStep_; }; diff --git a/corsika/framework/utility/CubicSolver.hpp b/corsika/framework/utility/CubicSolver.hpp new file mode 100644 index 000000000..d62757277 --- /dev/null +++ b/corsika/framework/utility/CubicSolver.hpp @@ -0,0 +1,88 @@ +/* + * (c) Copyright 2021 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 <vector> +#include <cmath> +#include <algorithm> +#include <numeric> +#include <complex> + +#include <corsika/framework/utility/QuadraticSolver.hpp> +#include <corsika/framework/core/Logging.hpp> + +#include <boost/multiprecision/cpp_bin_float.hpp> + +/** + @file CubicSolver.hpp + */ + +namespace corsika { + + /** + @ingroup Utility + @{ + */ + + namespace andre { + /** + Solve a x^3 + b x^2 + c x + d = 0 + */ + std::vector<double> solveP3(long double a, long double b, long double c, + long double d, double const epsilon = 1e-12); + } // namespace andre + + /** + Solve depressed cubic: x^3 + p x + q = 0 + */ + inline std::vector<double> solve_cubic_depressed_disciminant_real( + long double p, long double q, long double const disc, double const epsilon = 1e-12); + + std::vector<double> solve_cubic_depressed_real(long double p, long double q, + double const epsilon = 1e-12); + + /** + Solve a x^3 + b x^2 + c x + d = 0 + + Analytical approach. Not very stable in all conditions. + */ + std::vector<double> solve_cubic_real_analytic(long double a, long double b, + long double c, long double d, + double const epsilon = 1e-12); + + /** + @return a x^3 + b x^2 + c x + d + */ + long double cubic_function(long double x, long double a, long double b, long double c, + long double d); + + /** + @return 3 a x^2 + 2 b x + c + */ + long double cubic_function_dfdx(long double x, long double a, long double b, + long double c); + + /** + @return 6 a x + 2 b + */ + long double cubic_function_d2fd2x(long double x, long double a, long double b); + + /** + Iterative approach to solve: a x^3 + b x^2 + c x + d = 0 + + This is often fastest and most precise. + */ + std::vector<double> solve_cubic_real(long double a, long double b, long double c, + long double d, double const epsilon = 1e-12); + + //! @} + +} // namespace corsika + +#include <corsika/detail/framework/utility/CubicSolver.inl> diff --git a/corsika/framework/utility/LinearSolver.hpp b/corsika/framework/utility/LinearSolver.hpp new file mode 100644 index 000000000..95322958d --- /dev/null +++ b/corsika/framework/utility/LinearSolver.hpp @@ -0,0 +1,24 @@ +/* + * (c) Copyright 2021 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 <vector> +#include <cmath> +#include <complex> + +namespace corsika { + + std::vector<double> solve_linear_real(double a, double b, double const epsilon = 1e-12); + + std::vector<std::complex<double>> solve_linear(double a, double b, + double const epsilon = 1e-12); + +} // namespace corsika + +#include <corsika/detail/framework/utility/LinearSolver.inl> diff --git a/corsika/framework/utility/QuadraticSolver.hpp b/corsika/framework/utility/QuadraticSolver.hpp new file mode 100644 index 000000000..968c01bf1 --- /dev/null +++ b/corsika/framework/utility/QuadraticSolver.hpp @@ -0,0 +1,27 @@ +/* + * (c) Copyright 2021 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 <vector> +#include <cmath> +#include <complex> + +#include <corsika/framework/utility/LinearSolver.hpp> + +namespace corsika { + + std::vector<std::complex<double>> solve_quadratic(long double a, long double b, + long double c, + double const epsilon = 1e-12); + + std::vector<double> solve_quadratic_real(long double a, long double b, long double c, + double const epsilon = 1e-12); +} // namespace corsika + +#include <corsika/detail/framework/utility/QuadraticSolver.inl> diff --git a/corsika/framework/utility/QuarticSolver.hpp b/corsika/framework/utility/QuarticSolver.hpp index ad9484939..d6e20f02f 100644 --- a/corsika/framework/utility/QuarticSolver.hpp +++ b/corsika/framework/utility/QuarticSolver.hpp @@ -8,59 +8,46 @@ #pragma once +#include <corsika/framework/utility/CubicSolver.hpp> +#include <corsika/framework/utility/QuadraticSolver.hpp> + #include <complex> +#include <algorithm> +#include <cmath> +#include <numeric> /** * @file QuarticSolver.hpp - * @ingroup Utilities - * @{ - * \todo convert to class */ -namespace corsika::quartic_solver { - - 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; - } +namespace corsika { - //--------------------------------------------------------------------------- - // 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; - } + /** + * @ingroup Utilities + * @{ + */ - //--------------------------------------------------------------------------- - // 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; - } + namespace andre { - //--------------------------------------------------------------------------- - // 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 a*x^4 + b*x^3 + c*x^2 + d*x + e + Attention - this function returns dynamically allocated array. It has to be + released afterwards. + */ + std::vector<double> solve_quartic_real(long double a, long double b, long double c, + long double d, long double e, + double const epsilon = 1e-12); + } // namespace andre - //--------------------------------------------------------------------------- - // 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); + /** + solve quartic equation a*x^4 + b*x^3 + c*x^2 + d*x + e + */ + std::vector<double> solve_quartic_real(long double a, long double b, long double c, + long double d, long double e, + double const epsilon = 1e-12); //! @} -} // namespace corsika::quartic_solver +} // namespace corsika #include <corsika/detail/framework/utility/QuarticSolver.inl> diff --git a/corsika/modules/ObservationPlane.hpp b/corsika/modules/ObservationPlane.hpp index 8276c0d12..f7ccba999 100644 --- a/corsika/modules/ObservationPlane.hpp +++ b/corsika/modules/ObservationPlane.hpp @@ -20,9 +20,19 @@ namespace corsika { /** - * The ObservationPlane writes PDG codes, energies, and distances of particles to the - * central point of the plane into its output file. The particles are considered - * "absorbed" afterwards. + @ingroup Modules + @{ + + The ObservationPlane writes PDG codes, energies, and distances of particles to the + central point of the plane into its output file. The particles are considered + "absorbed" afterwards. + + **Note/Limitation:** as discussed in + https://gitlab.ikp.kit.edu/AirShowerPhysics/corsika/-/issues/397 + you cannot put two ObservationPlanes exactly on top of each + other. Even if one of them is "permeable". You have to put a + small gap in between the two plane in such a scenario, or develop + another more specialized output class. */ class ObservationPlane : public ContinuousProcess<ObservationPlane> { @@ -50,6 +60,7 @@ namespace corsika { DirectionVector const xAxis_; DirectionVector const yAxis_; }; + //! @} } // namespace corsika #include <corsika/detail/modules/ObservationPlane.inl> diff --git a/corsika/stack/NuclearStackExtension.hpp b/corsika/stack/NuclearStackExtension.hpp index 34bd1377d..19e9a44c3 100644 --- a/corsika/stack/NuclearStackExtension.hpp +++ b/corsika/stack/NuclearStackExtension.hpp @@ -92,6 +92,10 @@ namespace corsika::nuclear_stack { * Overwrite normal getParticleMass function with nuclear version */ HEPMassType getMass() const; + /** + * Overwrite normal getParticleCharge function with nuclear version + */ + ElectricChargeType getCharge() const; /** * Overwirte normal getChargeNumber function with nuclear version **/ diff --git a/corsika/stack/VectorStack.hpp b/corsika/stack/VectorStack.hpp index adaae996b..e5a765781 100644 --- a/corsika/stack/VectorStack.hpp +++ b/corsika/stack/VectorStack.hpp @@ -87,8 +87,14 @@ namespace corsika { return this->getMomentum() / this->getEnergy(); } + VelocityVector getVelocity() const { + return this->getMomentum() / this->getEnergy() * constants::c; + } + HEPMassType getMass() const { return get_mass(this->getPID()); } + ElectricChargeType getCharge() const { return get_charge(this->getPID()); } + int16_t getChargeNumber() const { return get_charge_number(this->getPID()); } ///@} }; diff --git a/examples/vertical_EAS.cpp b/examples/vertical_EAS.cpp index e5f6455d3..1b60722d8 100644 --- a/examples/vertical_EAS.cpp +++ b/examples/vertical_EAS.cpp @@ -145,7 +145,7 @@ int main(int argc, char** argv) { "environment setup: universe={}, layer1={}, layer2={}, layer3={}, layer4={}, " "layer5={}", fmt::ptr(env.getUniverse()->getContainingNode( - Point(rootCS, {constants::EarthRadius::Mean + 130_km, 0_m, 0_m}))), + Point(rootCS, {constants::EarthRadius::Mean + 130000_km, 0_m, 0_m}))), fmt::ptr(env.getUniverse()->getContainingNode( Point(rootCS, {constants::EarthRadius::Mean + 110_km, 0_m, 0_m}))), fmt::ptr(env.getUniverse()->getContainingNode( @@ -289,6 +289,46 @@ int main(int argc, char** argv) { // setup particle stack, and add primary particle setup::Stack stack; stack.clear(); + unsigned short const A = std::stoi(std::string(argv[1])); + Code beamCode; + HEPEnergyType mass; + unsigned short Z = 0; + if (A > 0) { + beamCode = Code::Nucleus; + Z = std::stoi(std::string(argv[2])); + mass = get_nucleus_mass(A, Z); + } else { + int pdg = std::stoi(std::string(argv[2])); + beamCode = convert_from_PDG(PDGCode(pdg)); + mass = get_mass(beamCode); + } + HEPEnergyType const E0 = 1_GeV * std::stof(std::string(argv[3])); + double theta = 0.; + auto const thetaRad = theta / 180. * M_PI; + + auto elab2plab = [](HEPEnergyType Elab, HEPMassType m) { + return sqrt((Elab - m) * (Elab + m)); + }; + HEPMomentumType P0 = elab2plab(E0, mass); + auto momentumComponents = [](double thetaRad, HEPMomentumType ptot) { + return std::make_tuple(ptot * sin(thetaRad), 0_eV, -ptot * cos(thetaRad)); + }; + + auto const [px, py, pz] = momentumComponents(thetaRad, P0); + auto plab = MomentumVector(rootCS, {px, py, pz}); + cout << "input particle: " << beamCode << endl; + cout << "input angles: theta=" << theta << endl; + cout << "input momentum: " << plab.getComponents() / 1_GeV + << ", norm = " << plab.getNorm() << endl; + + auto const observationHeight = 0_km + builder.getEarthRadius(); + auto const injectionHeight = 111.75_km + builder.getEarthRadius(); + auto const t = (injectionHeight - observationHeight) / cos(thetaRad); + Point const showerCore{rootCS, 0_m, 0_m, observationHeight}; + Point const injectionPos = + showerCore + DirectionVector{rootCS, {-sin(thetaRad), 0, cos(thetaRad)}} * t; + + std::cout << "point of injection: " << injectionPos.getCoordinates() << std::endl; if (A > 1) { stack.addParticle(std::make_tuple(beamCode, E0, plab, injectionPos, 0_ns, A, Z)); diff --git a/tests/framework/CMakeLists.txt b/tests/framework/CMakeLists.txt index 35f61737b..829709779 100644 --- a/tests/framework/CMakeLists.txt +++ b/tests/framework/CMakeLists.txt @@ -20,6 +20,7 @@ set (test_framework_sources testNullModel.cpp testHelix.cpp testInteractionCounter.cpp + testSolver.cpp ) CORSIKA_ADD_TEST (testFramework SOURCES ${test_framework_sources}) diff --git a/tests/framework/testCOMBoost.cpp b/tests/framework/testCOMBoost.cpp index caf6f0896..d092a4b61 100644 --- a/tests/framework/testCOMBoost.cpp +++ b/tests/framework/testCOMBoost.cpp @@ -40,7 +40,6 @@ auto const s = [](HEPEnergyType E, QuantityVector<hepmomentum_d> const& p) { TEST_CASE("rotation") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); // define projectile kinematics in lab frame HEPMassType const projectileMass = 1_GeV; @@ -172,7 +171,6 @@ TEST_CASE("rotation") { TEST_CASE("boosts") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); // define target kinematics in lab frame HEPMassType const targetMass = 1_GeV; @@ -329,7 +327,6 @@ TEST_CASE("boosts") { TEST_CASE("rest frame") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); HEPMassType const projectileMass = 1_GeV; HEPMomentumType const P0 = 1_TeV; diff --git a/tests/framework/testCascade.cpp b/tests/framework/testCascade.cpp index b860e9955..3e930911b 100644 --- a/tests/framework/testCascade.cpp +++ b/tests/framework/testCascade.cpp @@ -137,7 +137,6 @@ public: TEST_CASE("Cascade", "[Cascade]") { - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); logging::set_level(logging::level::trace); HEPEnergyType E0 = 100_GeV; diff --git a/tests/framework/testClassTimer.cpp b/tests/framework/testClassTimer.cpp index d51a56b1a..b88d2fc97 100644 --- a/tests/framework/testClassTimer.cpp +++ b/tests/framework/testClassTimer.cpp @@ -106,7 +106,6 @@ public: TEST_CASE("ClassTimer", "[Timer]") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); SECTION("Measure runtime of a function without arguments") { diff --git a/tests/framework/testCombinedStack.cpp b/tests/framework/testCombinedStack.cpp index 1f76fafc2..de6defc6a 100644 --- a/tests/framework/testCombinedStack.cpp +++ b/tests/framework/testCombinedStack.cpp @@ -89,7 +89,6 @@ using StackTest = CombinedStack<TestStackData, TestStackData2, CombinedTestInter TEST_CASE("Combined Stack", "[stack]") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); // helper function for sum over stack data auto sum = [](const StackTest& stack) { @@ -395,7 +394,6 @@ using Particle2 = typename StackTest2::particle_type; TEST_CASE("Combined Stack - secondary view") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); SECTION("create secondaries via secondaryview") { diff --git a/tests/framework/testCorsikaFenv.cpp b/tests/framework/testCorsikaFenv.cpp index 2b6cdb580..030d33dfa 100644 --- a/tests/framework/testCorsikaFenv.cpp +++ b/tests/framework/testCorsikaFenv.cpp @@ -22,7 +22,6 @@ static void handle_fpe(int /*signo*/) { gRESULT = 0; } TEST_CASE("CorsikaFenv", "[fenv]") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); SECTION("Enable all exceptions") { feenableexcept(FE_ALL_EXCEPT); } diff --git a/tests/framework/testFourVector.cpp b/tests/framework/testFourVector.cpp index 09b458295..489d6261b 100644 --- a/tests/framework/testFourVector.cpp +++ b/tests/framework/testFourVector.cpp @@ -20,7 +20,6 @@ using namespace corsika; TEST_CASE("four vectors") { - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); logging::set_level(logging::level::info); // this is just needed as a baseline diff --git a/tests/framework/testFunctionTimer.cpp b/tests/framework/testFunctionTimer.cpp index 46d97f710..1328ea7ba 100644 --- a/tests/framework/testFunctionTimer.cpp +++ b/tests/framework/testFunctionTimer.cpp @@ -32,7 +32,6 @@ public: TEST_CASE("FunctionTimer", "[Timer]") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); SECTION("Measure runtime of a free function") { diff --git a/tests/framework/testHelix.cpp b/tests/framework/testHelix.cpp index cbe997d20..042e4212f 100644 --- a/tests/framework/testHelix.cpp +++ b/tests/framework/testHelix.cpp @@ -22,7 +22,6 @@ double constexpr absMargin = 1.0e-8; TEST_CASE("Helix class") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); const CoordinateSystemPtr rootCS = get_root_CoordinateSystem(); Point r0(rootCS, {0_m, 0_m, 0_m}); diff --git a/tests/framework/testInteractionCounter.cpp b/tests/framework/testInteractionCounter.cpp index 556d4733b..4b4b7ff25 100644 --- a/tests/framework/testInteractionCounter.cpp +++ b/tests/framework/testInteractionCounter.cpp @@ -43,7 +43,6 @@ struct DummyProcess { TEST_CASE("InteractionCounter", "[process]") { - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); logging::set_level(logging::level::info); DummyProcess d; diff --git a/tests/framework/testLogging.cpp b/tests/framework/testLogging.cpp index b737d7dfa..92ad036e5 100644 --- a/tests/framework/testLogging.cpp +++ b/tests/framework/testLogging.cpp @@ -14,7 +14,7 @@ using namespace corsika; TEST_CASE("Logging", "[Logging]") { - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); + // corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); SECTION("top level functions using default corsika logger") { logging::info("(1) This is an info message!"); diff --git a/tests/framework/testNullModel.cpp b/tests/framework/testNullModel.cpp index 5b1f351e1..872fc34db 100644 --- a/tests/framework/testNullModel.cpp +++ b/tests/framework/testNullModel.cpp @@ -21,7 +21,6 @@ using namespace corsika; TEST_CASE("NullModel", "[processes]") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); SECTION("interface") { [[maybe_unused]] NullModel nm; diff --git a/tests/framework/testParticles.cpp b/tests/framework/testParticles.cpp index b74d836bc..d8c71e6e7 100644 --- a/tests/framework/testParticles.cpp +++ b/tests/framework/testParticles.cpp @@ -17,7 +17,6 @@ using namespace corsika; TEST_CASE("ParticleProperties", "[Particles]") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); SECTION("Types") { CHECK(Electron::code == Code::Electron); diff --git a/tests/framework/testRandom.cpp b/tests/framework/testRandom.cpp index 5774575e6..2d0c0720a 100644 --- a/tests/framework/testRandom.cpp +++ b/tests/framework/testRandom.cpp @@ -55,7 +55,6 @@ SCENARIO("random-number streams can be registered and retrieved") { TEST_CASE("UniformRealDistribution") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); std::mt19937 rng; @@ -101,7 +100,6 @@ TEST_CASE("UniformRealDistribution") { TEST_CASE("ExponentialDistribution") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); std::mt19937 rng; diff --git a/tests/framework/testSaveBoostHistogram.cpp b/tests/framework/testSaveBoostHistogram.cpp index d5c16438a..89182311a 100644 --- a/tests/framework/testSaveBoostHistogram.cpp +++ b/tests/framework/testSaveBoostHistogram.cpp @@ -16,7 +16,6 @@ namespace bh = boost::histogram; TEST_CASE("SaveHistogram") { - corsika::corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); corsika::logging::set_level(corsika::logging::level::info); std::mt19937 rng; diff --git a/tests/framework/testSecondaryView.cpp b/tests/framework/testSecondaryView.cpp index 87cfd91c0..fa113f88b 100644 --- a/tests/framework/testSecondaryView.cpp +++ b/tests/framework/testSecondaryView.cpp @@ -44,7 +44,6 @@ using Particle = typename StackTest::particle_type; TEST_CASE("SecondaryStack", "[stack]") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); // helper function for sum over stack data auto sum = [](const StackTest& stack) { diff --git a/tests/framework/testSolver.cpp b/tests/framework/testSolver.cpp new file mode 100644 index 000000000..52f1fa13f --- /dev/null +++ b/tests/framework/testSolver.cpp @@ -0,0 +1,278 @@ +/* + * (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. + */ + +#include <catch2/catch.hpp> + +#include <cmath> +#include <vector> +#include <complex> +#include <algorithm> + +#include <corsika/framework/core/Logging.hpp> +#include <corsika/framework/utility/QuadraticSolver.hpp> +#include <corsika/framework/utility/LinearSolver.hpp> +#include <corsika/framework/utility/CubicSolver.hpp> +#include <corsika/framework/utility/QuarticSolver.hpp> + +using namespace corsika; +using namespace std; + +double pol4(long double x, long double a, long double b, long double c, long double d, + long double e) { + return std::pow(x, 4) * a + std::pow(x, 3) * b + std::pow(x, 2) * c + x * d + e; +} + +void remove_duplicates(vector<double>& v, double const eps) { + std::sort(v.begin(), v.end()); + v.erase(std::unique(v.begin(), v.end(), + [eps](auto v1, auto v2) { + return (std::abs(v2) > eps ? std::abs(2 * (v1 - v2) / (v1 + v2)) < + eps // relative + : std::abs(v1 - v2) < eps); // absolute + }), + v.end()); +} + +TEST_CASE("Solver") { + + logging::set_level(logging::level::trace); + + double epsilon_check = 1e-3; // for catch2 asserts + + SECTION("linear") { + + std::vector<std::pair<double, double>> vals = {{13.33, 8338.3}, + {-333.99, -8.15}, + {-58633.3, 2343.54}, + {87632.87, -982.37}, + {7e8, -1e-7}}; + + for (auto v : vals) { + + { + double a = v.first; + double b = v.second; + + vector<double> s1 = solve_linear_real(a, b); + + CORSIKA_LOG_INFO("linear: a={}, b={}, N={}, s1[0]={}", a, b, s1.size(), s1[0]); + + double expected = -b / a; + + CHECK(s1.size() == 1); + CHECK((s1[0] == Approx(expected).epsilon(epsilon_check))); + + vector<complex<double>> s2 = solve_linear(a, b); + CHECK(s2.size() == 1); + CHECK((s2[0].real() == Approx(expected).epsilon(epsilon_check))); + } + } + + CHECK(solve_linear_real(0, 55.).size() == 0); + } + + SECTION("quadratic") { + + // tests of type: (x-z1)*(x-z2) = 0 --> x1=z1, x2=z2 and a=1, b=-z2-z1, + // c=z1*z2 + + std::vector<std::vector<double>> zeros = { + {13.33, 8338.3}, {-333.99, -8.15}, {-58633.3, 2343.54}, {87632.87, -982.37}, + {1.3e-6, 5e7}, {-4.2e-7, 65e6}, {0.1, -13e-5}, {7e8, -1e-7}}; + + for (unsigned int idegree = 0; idegree < 2; ++idegree) { + CORSIKA_LOG_INFO("------------- quadratic idegree={}", idegree); + for (auto z : zeros) { + + { + long double const z1 = z[0]; + long double const z2 = idegree <= 0 ? z1 : z[1]; + + long double a = 1; + long double b = -z1 - z2; + long double c = z1 * z2; + + std::vector<double> s1 = solve_quadratic_real(a, b, c); + remove_duplicates(s1, epsilon_check); + + CORSIKA_LOG_INFO("quadratic: z1={}, z2={}, N={}, s1=[{}]", z1, z2, s1.size(), + fmt::join(s1, ", ")); + + CHECK(s1.size() == idegree + 1); + for (auto value : s1) { + if (std::abs(value) < epsilon_check) { + CHECK(((value == Approx(z1).margin(epsilon_check)) || + (value == Approx(z2).margin(epsilon_check)))); + } else { + CHECK(((value == Approx(z1).epsilon(epsilon_check)) || + (value == Approx(z2).epsilon(epsilon_check)))); + } + } + /* + std::vector<complex<double>> s2 = solve_quadratic(a, b, c, epsilon); + CHECK(s2.size() == idegree + 1); + for (auto value : s2) { + if (std::abs(value) < epsilon) { + CHECK(((value == Approx(z1).margin(epsilon_check)) || + (value == Approx(z2).margin(epsilon_check)))); + } else { + CHECK(((value == Approx(z1).epsilon(epsilon_check)) || + (value == Approx(z2).epsilon(epsilon_check)))); + } + } + }*/ + } + } + } + } + + SECTION("cubic") { + + // tests of type: + // (x-z1) * (x-z2) * (x-z3) = 0 --> x1=z1, x2=z2, x3=z3 and + + // (x^2 - x*(z1+z2) + z1*z2) * (x-z3) = + // x^3 - x^2*(z1+z2) + x*z1*z2 - x^2 z3 + x*(z1+z2)*z3 - z1*z2*z3 = + // x^3 + x^2 (-z3-z1-z2) + x (z1*z2 + (z1+z2)*z3) - z1*z2*z3 + + epsilon_check = 1e-2; // for catch2 asserts + + std::vector<std::vector<double>> zeros = { + {13.33, 838.3, 44.}, {-333.99, -8.15, -33e8}, {-58633.3, 2343.54, -1e-5}, + {87632.87, -982.37, 0.}, {1.3e-4, 5e7, 6.6e9}, {-4.2e-7, 65e6, 433.3334}, + {23e-1, -13e-2, 10.}, {7e8, -1e-7, 1e8}}; + + for (unsigned int idegree = 0; idegree < 3; ++idegree) { + CORSIKA_LOG_INFO("------------- cubic idegree={}", idegree); + for (auto z : zeros) { + + { + long double const z1 = z[0]; + long double const z2 = idegree <= 0 ? z1 : z[1]; + long double const z3 = idegree <= 1 ? z1 : z[2]; + + long double const a = 1; + long double const b = -z1 - z2 - z3; + long double const c = z1 * z2 + (z1 + z2) * z3; + long double const d = -z1 * z2 * z3; + // + CORSIKA_LOG_INFO( + "cubic: z1={}, z2={}, z3={}, " + "a={}, b={}, c={}, d={}", + z1, z2, z3, a, b, c, d); + + vector<double> s1 = solve_cubic_real(a, b, c, d); + remove_duplicates(s1, epsilon_check); + + CORSIKA_LOG_INFO("N={}, s1=[{}]", s1.size(), fmt::join(s1, ", ")); + + CHECK(s1.size() == idegree + 1); + for (double value : s1) { + CORSIKA_LOG_INFO("value={}, z1={} z2={} z3={} eps_check={}", value, z1, z2, + z3, epsilon_check); + if (std::abs(value) < epsilon_check) { + CHECK(((value == Approx(z1).margin(epsilon_check)) || + (value == Approx(z2).margin(epsilon_check)) || + (value == Approx(z3).margin(epsilon_check)))); + } else { + CHECK(((value == Approx(z1).epsilon(epsilon_check)) || + (value == Approx(z2).epsilon(epsilon_check)) || + (value == Approx(z3).epsilon(epsilon_check)))); + } + } + } + } + } + } // cubic + + SECTION("quartic") { + + epsilon_check = 1e-4; // for catch2 asserts + + // **clang-format-off** + // tests of type: + // (x-z1) * (x-z2) * (x-z3) * (x-z4) = 0 --> x1=z1, x2=z2, x3=z3, x4=z4 and + + // (x^2 - x (z1+z2) + z1*z2) * (x-z3) * + // (x-z4) = (x^3 - x^2 (z1+z2) + x z1*z2 - x^2 z3 + x*(z1+z2)*z3 - + // z1*z2*z3) * (x-z4) = (x^3 + x^2 (-z1-z2-z3) + x (z1*z2 + (z1+z2)*z3) - + // z1*z2*z3) * (x-z4) + // = + // + // x^4 + x^3 (-z1-z2-z3) + x^2 (z1*z2 + (z1+z2)*z3) - x z1*z2*z3 + // - x^3 z4 - x^2 (-z1-z2-z3)*z4 - x (z1*z2 + (z1+z2)*z3)*z4 + // + z1*z2*z3*z4 + // + // = x^4 + // + x^3 (-z1-z2-z3-z4) + // + x^2 (z1*z2 + (z1+z2)*z3 + (z1+z2+z3)*z4) + // - x (z1*z2*z3 + (z1*z2 + (z1+z2)*z3)*z4)) + // + z1*z2*z3*z4 + // **clang-format-on** + + std::vector<std::vector<double>> zeros = { + {13.33, 838.3, 44., 2.3}, {-3333.99, -8.15, -33e4, 8.8e3}, + {-58633.3, 2343.54, -1e-1, 0.}, {87632.87, -982.37, 10., 1e-2}, + {1.3e2, 5e5, 6.6e5, 1e3}, {-4.9, 65e2, 433.3334, 6e5}, + {23e-1, -13e-2, 10., 3.4e6}, {7e6, -1e-1, 1e5, 2.55e4}}; + + for (unsigned int idegree = 2; idegree < 4; + ++idegree) { // idegree==1 is not very stable !!!!!!! + CORSIKA_LOG_INFO("------------- quartic idegree={}", idegree); + for (auto z : zeros) { + + { + long double const z1 = z[0]; + long double const z2 = idegree <= 0 ? z1 : z[1]; + long double const z3 = idegree <= 1 ? z1 : z[2]; + long double const z4 = idegree <= 2 ? z1 : z[3]; + + long double const a = 1; + long double const b = -z1 - z2 - z3 - z4; + long double const c = z1 * z2 + (z1 + z2) * z3 + (z1 + z2 + z3) * z4; + long double const d = -z1 * z2 * z3 - (z1 * z2 + (z1 + z2) * z3) * z4; + long double const e = z1 * z2 * z3 * z4; + + // + CORSIKA_LOG_INFO( + "quartic: z1={}, z2={}, z3={}, z4={}, " + "a={}, b={}, c={}, d={}, e={}", + z1, z2, z3, z4, a, b, c, d, e); + + // make sure the tested cases are all ZERO (printout) + CORSIKA_LOG_INFO("quartic trace: {} {} {} {}", pol4(z1, a, b, c, d, e), + pol4(z2, a, b, c, d, e), pol4(z3, a, b, c, d, e), + pol4(z4, a, b, c, d, e)); + + vector<double> s1 = andre::solve_quartic_real(a, b, c, d, e); + // vector<double> s1 = solve_quartic_real(a, b, c, d, e, epsilon); + remove_duplicates(s1, epsilon_check); + + CORSIKA_LOG_INFO("N={}, s1=[{}]", s1.size(), fmt::join(s1, ", ")); + + CHECK(s1.size() == idegree + 1); + for (double value : s1) { + CORSIKA_LOG_INFO("value={}, z1={} z2={} z3={} z4={} eps_check={}", value, z1, + z2, z3, z4, epsilon_check); + if (std::abs(value) < epsilon_check) { + CHECK(((value == Approx(z1).margin(epsilon_check)) || + (value == Approx(z2).margin(epsilon_check)) || + (value == Approx(z3).margin(epsilon_check)) || + (value == Approx(z4).margin(epsilon_check)))); + } else { + CHECK(((value == Approx(z1).epsilon(epsilon_check)) || + (value == Approx(z2).epsilon(epsilon_check)) || + (value == Approx(z3).epsilon(epsilon_check)) || + (value == Approx(z4).epsilon(epsilon_check)))); + } + } + } + } + } + } // quartic +} diff --git a/tests/framework/testStackInterface.cpp b/tests/framework/testStackInterface.cpp index b37dc495d..4171647af 100644 --- a/tests/framework/testStackInterface.cpp +++ b/tests/framework/testStackInterface.cpp @@ -27,7 +27,6 @@ typedef Stack<TestStackData, TestParticleInterface> StackTest; TEST_CASE("Stack", "[Stack]") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); // helper function for sum over stack data auto sum = [](const StackTest& stack) { diff --git a/tests/framework/testUnits.cpp b/tests/framework/testUnits.cpp index 8d15544ac..5c90aa9cd 100644 --- a/tests/framework/testUnits.cpp +++ b/tests/framework/testUnits.cpp @@ -19,7 +19,6 @@ using namespace corsika; TEST_CASE("PhysicalUnits", "[Units]") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); SECTION("Consistency") { CHECK(1_m / 1_m == Approx(1)); diff --git a/tests/modules/testCONEX.cpp b/tests/modules/testCONEX.cpp index 34718d69b..2128e0a47 100644 --- a/tests/modules/testCONEX.cpp +++ b/tests/modules/testCONEX.cpp @@ -39,7 +39,6 @@ using MExtraEnvirnoment = MediumPropertyModel<UniformMagneticField<T>>; TEST_CASE("CONEXSourceCut") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); RNGManager::getInstance().registerRandomStream("cascade"); RNGManager::getInstance().registerRandomStream("sibyll"); @@ -123,7 +122,6 @@ TEST_CASE("CONEXSourceCut") { TEST_CASE("ConexOutput", "[output validation]") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); auto file = GENERATE(as<std::string>{}, "conex_fit", "conex_output"); diff --git a/tests/modules/testExecTime.cpp b/tests/modules/testExecTime.cpp index 68a85d81b..4ebc03342 100644 --- a/tests/modules/testExecTime.cpp +++ b/tests/modules/testExecTime.cpp @@ -27,7 +27,6 @@ using namespace corsika::process::example_processors; TEST_CASE("Timing process", "[proccesses][analytic_processors ExecTime]") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); int tmp = 0; diff --git a/tests/modules/testNullModel.cpp b/tests/modules/testNullModel.cpp index a24433b08..6485b7df9 100644 --- a/tests/modules/testNullModel.cpp +++ b/tests/modules/testNullModel.cpp @@ -14,7 +14,6 @@ using namespace corsika; TEST_CASE("NullModel", "[processes]") { - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); logging::set_level(logging::level::trace); SECTION("interface") { diff --git a/tests/modules/testOnShellCheck.cpp b/tests/modules/testOnShellCheck.cpp index eccf73f28..e28c5ffcd 100644 --- a/tests/modules/testOnShellCheck.cpp +++ b/tests/modules/testOnShellCheck.cpp @@ -25,7 +25,6 @@ using namespace corsika; TEST_CASE("OnShellCheck", "[processes]") { logging::set_level(logging::level::debug); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); feenableexcept(FE_INVALID); using EnvType = setup::Environment; diff --git a/tests/modules/testQGSJetII.cpp b/tests/modules/testQGSJetII.cpp index 4a604fd8b..3efea7742 100644 --- a/tests/modules/testQGSJetII.cpp +++ b/tests/modules/testQGSJetII.cpp @@ -49,7 +49,6 @@ auto sumMomentum(TStackView const& view, CoordinateSystemPtr const& vCS) { TEST_CASE("CORSIKA_DATA", "[processes]") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); SECTION("check CORSIKA_DATA") { @@ -68,7 +67,6 @@ TEST_CASE("CORSIKA_DATA", "[processes]") { TEST_CASE("QgsjetII", "[processes]") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); SECTION("Corsika -> QgsjetII") { CHECK(corsika::qgsjetII::convertToQgsjetII(PiMinus::code) == @@ -133,7 +131,6 @@ TEST_CASE("QgsjetII", "[processes]") { TEST_CASE("QgsjetIIInterface", "[processes]") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Oxygen); [[maybe_unused]] auto const& env_dummy = env; @@ -184,7 +181,7 @@ TEST_CASE("QgsjetIIInterface", "[processes]") { corsika::qgsjetII::Interaction model; model.doInteraction(view); // this also should produce some fragments - CHECK(view.getSize() == Approx(188).margin(2)); // this is not physics validation + CHECK(view.getSize() == Approx(228).margin(10)); // 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 @@ -240,7 +237,7 @@ TEST_CASE("QgsjetIIInterface", "[processes]") { setup::StackView& view = *(secViewPtr.get()); corsika::qgsjetII::Interaction model; model.doInteraction(view); - CHECK(view.getSize() == Approx(7).margin(2)); // this is not physics validation + CHECK(view.getSize() == Approx(12).margin(4)); // this is not physics validation } { // Lambda is internally converted into neutron auto [stackPtr, secViewPtr] = setup::testing::setup_stack( @@ -249,11 +246,11 @@ TEST_CASE("QgsjetIIInterface", "[processes]") { setup::StackView& view = *(secViewPtr.get()); corsika::qgsjetII::Interaction model; model.doInteraction(view); - CHECK(view.getSize() == Approx(25).margin(3)); // this is not physics validation + CHECK(view.getSize() == Approx(10).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, + Code::Lambda0Bar, 0, 0, 1000_GeV, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); setup::StackView& view = *(secViewPtr.get()); corsika::qgsjetII::Interaction model; diff --git a/tests/modules/testSibyll.cpp b/tests/modules/testSibyll.cpp index dc237b4a3..dc5249a72 100644 --- a/tests/modules/testSibyll.cpp +++ b/tests/modules/testSibyll.cpp @@ -32,7 +32,6 @@ using namespace corsika::sibyll; TEST_CASE("Sibyll", "[processes]") { - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); logging::set_level(logging::level::trace); SECTION("Sibyll -> Corsika") { @@ -99,7 +98,6 @@ auto sumMomentum(TStackView const& view, CoordinateSystemPtr const& vCS) { TEST_CASE("SibyllInterface", "[processes]") { - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); logging::set_level(logging::level::trace); auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Oxygen); diff --git a/tests/modules/testStackInspector.cpp b/tests/modules/testStackInspector.cpp index 00187ed9a..0b2a3ab7b 100644 --- a/tests/modules/testStackInspector.cpp +++ b/tests/modules/testStackInspector.cpp @@ -24,7 +24,6 @@ using namespace corsika; TEST_CASE("StackInspector", "[processes]") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); auto const& rootCS = get_root_CoordinateSystem(); Point const origin(rootCS, {0_m, 0_m, 0_m}); diff --git a/tests/modules/testTracking.cpp b/tests/modules/testTracking.cpp index 87cd51e93..3f1d6ecaa 100644 --- a/tests/modules/testTracking.cpp +++ b/tests/modules/testTracking.cpp @@ -43,8 +43,6 @@ TEMPLATE_TEST_CASE("TrackingLeapfrog_Curved", "tracking", tracking_leapfrog_straight::Tracking, tracking_line::Tracking) { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); - logging::set_level(logging::level::trace); const HEPEnergyType P0 = 10_GeV; @@ -78,8 +76,9 @@ TEMPLATE_TEST_CASE("TrackingLeapfrog_Curved", "tracking", int deflect = 0; if (chargeNumber != 0 and Bfield != 0_T) { deflect = -sgn(chargeNumber) * sgn(Bfield / 1_T); // direction of deflection - LengthType const gyroradius = - P0 * 1_V / (constants::c * abs(chargeNumber) * abs(Bfield) * 1_eV); + LengthType const gyroradius = (convert_HEP_to_SI<MassType::dimension_type>(P0) * + constants::c / (abs(get_charge(PID)) * abs(Bfield))); + CORSIKA_LOG_DEBUG("Rg={} deflect={}", gyroradius, deflect); radius = gyroradius; } @@ -123,7 +122,7 @@ TEMPLATE_TEST_CASE("TrackingLeapfrog_Curved", "tracking", particle.setMomentum(traj.getDirection(1) * particle.getMomentum().getNorm()); if (outer) { // now we know we are in target volume, depending on "outer" - CHECK(traj.getLength(1) == 0_m); + CHECK(traj.getLength(1) / 1_m == Approx(0).margin(1e-3)); CHECK(nextVol == targetPtr); } // move forward, until we leave target volume @@ -133,6 +132,11 @@ TEMPLATE_TEST_CASE("TrackingLeapfrog_Curved", "tracking", particle.setNode(nextVol); particle.setPosition(traj2.getPosition(1)); particle.setMomentum(traj2.getDirection(1) * particle.getMomentum().getNorm()); + CORSIKA_LOG_DEBUG("pos={}, p={}, |p|={} |v|={}, delta-l={}, delta-t={}", + particle.getPosition(), particle.getMomentum(), + particle.getMomentum().getNorm(), + particle.getVelocity().getNorm(), traj2.getLength(1), + traj2.getLength(1) / particle.getVelocity().getNorm()); } CHECK(nextVol == worldPtr); diff --git a/tests/modules/testUrQMD.cpp b/tests/modules/testUrQMD.cpp index 0f9f4a862..fd3ba64fe 100644 --- a/tests/modules/testUrQMD.cpp +++ b/tests/modules/testUrQMD.cpp @@ -59,7 +59,6 @@ auto sumMomentum(TStackView const& view, CoordinateSystemPtr const& vCS) { TEST_CASE("UrQMD") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); SECTION("conversion") { CHECK_THROWS(corsika::urqmd::convertFromUrQMD(106, 0)); @@ -227,22 +226,4 @@ 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)); - } } -- GitLab