diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index aa52dd3f38b47442e0c1d3697735b2306dfc0d22..a52219a52896ab6382d6ab72148da5288d041124 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -16,6 +16,7 @@ variables: # _alternatively_ corsika-data can be downloaded as submodule: GIT_SUBMODULE_STRATEGY: normal # none: we get the submodules in before_script, # normal: get submodules automatically + CTEST_OUTPUT_ON_FAILURE: 1 # @@ -251,6 +252,8 @@ test-clang-8: artifacts: when: always expire_in: 3 days + paths: + - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml reports: junit: - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml @@ -311,6 +314,7 @@ build_test-clang-8: - if: $CI_COMMIT_TAG when: manual allow_failure: true + allow_failure: true artifacts: when: always expire_in: 3 days @@ -319,6 +323,7 @@ build_test-clang-8: - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml paths: - ${CI_PROJECT_DIR}/build/build_examples/examples.log.gz + - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml cache: paths: - ${CI_PROJECT_DIR}/build/ @@ -440,6 +445,7 @@ install-clang-8: - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml paths: - ${CI_PROJECT_DIR}/build/build_examples/examples.log.gz + - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml # release for gcc release-full-u-18_04: diff --git a/CMakeModules/FindCONEX.cmake b/cmake/FindCONEX.cmake similarity index 100% rename from CMakeModules/FindCONEX.cmake rename to cmake/FindCONEX.cmake diff --git a/conanfile.txt b/conanfile.txt index 85c1238b40b290ca181660b0a8bda665b1c88c5b..78cda273ada5bf3c944cb45f6f22df53d0567a8c 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -1,9 +1,28 @@ [requires] -spdlog/1.8.0 +readline/8.0 # this is only needed to fix a missing dependency in "bison" which is pulled-in by arrow +spdlog/1.8.5 catch2/2.13.3 eigen/3.3.8 -boost/1.74.0 +boost/1.76.0 zlib/1.2.11 +proposal/7.0.2 +yaml-cpp/0.6.3 +arrow/2.0.0 [generators] cmake + +[options] +readline:shared=True +arrow:shared=False +arrow:parquet=True +arrow:fPIC=False +arrow:with_re2=False +arrow:with_protobuf=False +arrow:with_openssl=False +arrow:with_gflags=False +arrow:with_glog=False +arrow:with_grpc=False +arrow:with_utf8proc=False +arrow:with_zstd=False +arrow:with_bz2=False diff --git a/corsika/detail/framework/core/Cascade.inl b/corsika/detail/framework/core/Cascade.inl index 28306c590bb7da30d19d70f7ddfa19917ce525a0..32127908c714d79dfdd7e22e74615aed449b1b7c 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 * 0.99 > initial_inv_decay_time) { + CORSIKA_LOG_WARN( + "Decay time decreased during step! This leads to un-physical step length. " + "delta_inverse_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,31 @@ 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 * 0.99 > initial_inv_int_length) { + CORSIKA_LOG_WARN( + "Interaction length decreased during step! This leads to un-physical step " + "length. delta_innverse_interaction_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/process/ProcessSequence.inl b/corsika/detail/framework/process/ProcessSequence.inl index 6c9bd789289a6fa24204bfe10df34e5413b5b6fa..5eb3d20ae785b7645101b362cb4697282a29ecdd 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); } @@ -350,9 +350,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 (is_process_v<process1_type> && - 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 @@ -376,9 +374,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 (is_process_v<process2_type> && - 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()); // soon as SecondaryView::parent() is migrated! diff --git a/corsika/detail/framework/process/SwitchProcessSequence.inl b/corsika/detail/framework/process/SwitchProcessSequence.inl index 2f87ade6d329ab249f5a61f21b79c72d07d315fd..634395f55e0a59524263df2f9d72ebe80cac8be6 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 0000000000000000000000000000000000000000..3a68a3d6ebcf39290807c3402c09fcfccfe8ab6e --- /dev/null +++ b/corsika/detail/framework/utility/CubicSolver.inl @@ -0,0 +1,298 @@ +/* + * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu + * + * This software is distributed under the terms of the GNU General Public + * Licence version 3 (GPL Version 3). See file LICENSE for a full version of + * the license. + */ + +#pragma once + +#include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/framework/utility/CubicSolver.hpp> +#include <cmath> + +namespace corsika { + + namespace andre { + + //--------------------------------------------------------------------------- + // solve cubic equation A x^3 + B*x^2 + C*x + D = 0 + inline std::vector<double> solve_cubic_real_analytic(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 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; + + 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 term1 = -cbrt(std::fabs(r) + std::sqrt(-disc)); + if (r < 0) term1 = -term1; + long double term2 = (0 == term1 ? 0 : q / term1); + + a /= 3; + long double test = 0.5 * std::sqrt(3.) * (term1 - term2); + if (std::fabs(test) < epsilon) { + return {double((term1 + term2) - 1), double(-0.5 * (term1 + term2) - a)}; + } + + return {double((term1 + term2) - 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, ", "), + static_pow<3>(z) * a + static_pow<2>(z) * b + z * c + d); + } + CORSIKA_LOG_TRACE("cubic: solve_cubic_real returns={}", fmt::join(zs, ", ")); + return zs; + } + + template <typename T> // T must be floating point type + inline T cubic_function(T x, T a, T b, T c, T d) { + T x2 = x * x; + return x2 * x * a + x2 * b + x * c + d; + } + + template <typename T> // T must be floating point type + inline T cubic_function_dfdx(T x, T a, T b, T c) { + T x2 = x * x; + return x2 * a * 3 + x * b * 2 + c; + } + + template <typename T> // T must be floating point type + inline T cubic_function_d2fd2x(T x, T a, T 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 / (static_pow<2>(f_prime_x1) - 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 0000000000000000000000000000000000000000..6eccee91ce4614d5cf95cd0948c6497824741213 --- /dev/null +++ b/corsika/detail/framework/utility/LinearSolver.inl @@ -0,0 +1,35 @@ +/* + * (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) { + + if (a == 0) { + 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) { + + if (std::abs(a) == 0) { + return {}; // no (b!=0), or infinite number (b==0) of solutions.... + } + + return {std::complex<double>(-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 0000000000000000000000000000000000000000..9c9759e975ae6d322dee7a478f935c095bdf0ff9 --- /dev/null +++ b/corsika/detail/framework/utility/QuadraticSolver.inl @@ -0,0 +1,83 @@ +/* + * (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. + */ + +#include <corsika/framework/core/PhysicalUnits.hpp> + +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); } + + if (std::abs(c) < epsilon) { + std::vector<std::complex<double>> lin_result = solve_linear(a, b); + lin_result.push_back({0.}); + return lin_result; + } + + long double const radicant = static_pow<2>(b) - 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); } + if (std::abs(c) < epsilon) { + std::vector<double> lin_result = solve_linear_real(a, b); + 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 a82abf7ac022c3c99f6e342f88eb35f2ddaa6425..988bbe80b315d59344c8c71f8960ac5833e270aa 100644 --- a/corsika/detail/framework/utility/QuarticSolver.inl +++ b/corsika/detail/framework/utility/QuarticSolver.inl @@ -8,128 +8,154 @@ #pragma once +#include <corsika/framework/core/PhysicalUnits.hpp> +#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 sqDet1 = sqrt(Det); + q1 = (y + sqDet1) * 0.5; + q2 = (y - sqDet1) * 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 + + 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 = static_pow<2>(p); + long double const q2 = static_pow<2>(q); - 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)={}", (static_pow<3>(v) + static_pow<2>(v) * 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 = static_pow<2>(b); + long double const b3 = static_pow<3>(b); + long double const b4 = static_pow<4>(b); + long double const a2 = static_pow<2>(a); + long double const a3 = static_pow<3>(a); + long double const a4 = static_pow<4>(a); + + 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/LongitudinalProfile.inl b/corsika/detail/modules/LongitudinalProfile.inl index 48deafe6e79a474f06ecf664d9b70640b8e7756b..5b1f8438bc0438c45940e34c9502cd31ec637196 100644 --- a/corsika/detail/modules/LongitudinalProfile.inl +++ b/corsika/detail/modules/LongitudinalProfile.inl @@ -35,15 +35,15 @@ namespace corsika { GrammageType const grammageStart = shower_axis_.getProjectedX(vTrack.getPosition(0)); GrammageType const grammageEnd = shower_axis_.getProjectedX(vTrack.getPosition(1)); - CORSIKA_LOG_TRACE("pos1={} m, pos2={}, X1={} g/cm2, X2={} g/cm2", + CORSIKA_LOG_DEBUG("longprof: pos1={} m, pos2={}, X1={} g/cm2, X2={} g/cm2", vTrack.getPosition(0).getCoordinates() / 1_m, vTrack.getPosition(1).getCoordinates() / 1_m, grammageStart / 1_g * square(1_cm), grammageEnd / 1_g * square(1_cm)); // Note: particle may go also "upward", thus, grammageEnd<grammageStart - const int binStart = std::ceil(grammageStart / dX_); - const int binEnd = std::floor(grammageEnd / dX_); + int const binStart = std::ceil(grammageStart / dX_); + int const binEnd = std::floor(grammageEnd / dX_); for (int b = binStart; b <= binEnd; ++b) { if (pid == Code::Gamma) { @@ -66,6 +66,7 @@ namespace corsika { inline void LongitudinalProfile::save(std::string const& filename, const int width, const int precision) { + CORSIKA_LOG_DEBUG("Write longprof to {}", filename); std::ofstream f{filename}; f << "# X / g·cm¯², gamma, e+, e-, mu+, mu-, all hadrons" << std::endl; for (size_t b = 0; b < profiles_.size(); ++b) { diff --git a/corsika/detail/modules/ObservationPlane.inl b/corsika/detail/modules/ObservationPlane.inl index c4da50e940f88e3b8a3d3e9f36ac0018eeb09466..e5be30bedf2f81b65d68faeca314669c58c31ac6 100644 --- a/corsika/detail/modules/ObservationPlane.inl +++ b/corsika/detail/modules/ObservationPlane.inl @@ -24,6 +24,7 @@ namespace corsika { , count_ground_(0) , xAxis_(x_axis.normalized()) , yAxis_(obsPlane.getNormal().cross(xAxis_)) { + CORSIKA_LOG_DEBUG("Plane height: {}", obsPlane.getCenter()); outputStream_ << "#PDG code, energy / eV, x distance / m, y distance / m" << std::endl; } @@ -35,7 +36,18 @@ namespace corsika { /* The current step did not yet reach the ObservationPlane, do nothing now and wait: */ - if (!stepLimit) { return ProcessReturn::Ok; } + if (!stepLimit) { +#ifdef DEBUG + if (deleteOnHit_) { + LengthType const check = + (particle.getPosition() - plane_.getCenter()).dot(plane_.getNormal()); + if (check < 0_m) { + CORSIKA_LOG_DEBUG("PARTICLE AVOIDED OBSERVATIONPLANE {}", check); + } + } +#endif + return ProcessReturn::Ok; + } HEPEnergyType const energy = particle.getEnergy(); Point const pointOfIntersection = particle.getPosition(); @@ -45,6 +57,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 +72,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/StackInspector.inl b/corsika/detail/modules/StackInspector.inl index caae6c59c8cb43b01cd1e9789366b51db2f9ec14..2d9c8238a4a5bb74b6ae80d6b218df0f1ef31221 100644 --- a/corsika/detail/modules/StackInspector.inl +++ b/corsika/detail/modules/StackInspector.inl @@ -72,13 +72,13 @@ namespace corsika { CORSIKA_LOG_INFO( "StackInspector: " - " time= {}" - ", running= {} seconds" + " time={}" + ", running={} seconds" " ( {}%)" - ", nStep= {}" - ", stackSize= {}" - ", Estack= {} GeV" - ", ETA=", + ", nStep={}" + ", stackSize={}" + ", Estack={} GeV" + ", ETA={}", std::put_time(std::localtime(&now_time), "%T"), elapsed_seconds.count(), int(progress * 100), getStep(), vS.getSize(), Etot / 1_GeV, std::put_time(std::localtime(&eta_time), "%T")); diff --git a/corsika/detail/modules/TrackWriter.inl b/corsika/detail/modules/TrackWriter.inl index ebd52339e72eddde23f1f50dc66e709712165963..e55d40cb9cfb64d23aaccd21fc11347e10035226 100644 --- a/corsika/detail/modules/TrackWriter.inl +++ b/corsika/detail/modules/TrackWriter.inl @@ -30,9 +30,14 @@ namespace corsika { << '\n'; } + inline TrackWriter::~TrackWriter() { file_.close(); } + template <typename TParticle, typename TTrack> inline ProcessReturn TrackWriter::doContinuous(TParticle const& vP, TTrack const& vT, bool const) { + + CORSIKA_LOG_DEBUG("TrackWriter"); + auto const start = vT.getPosition(0).getCoordinates(); auto const delta = vT.getPosition(1).getCoordinates() - start; auto const pdg = static_cast<int>(get_PDG(vP.getPID())); diff --git a/corsika/detail/modules/energy_loss/BetheBlochPDG.inl b/corsika/detail/modules/energy_loss/BetheBlochPDG.inl index 73fbf351707c35c894dd8cf48e0d250589909527..310613d24d2d8fcbd75402822773891b8374dfca 100644 --- a/corsika/detail/modules/energy_loss/BetheBlochPDG.inl +++ b/corsika/detail/modules/energy_loss/BetheBlochPDG.inl @@ -184,9 +184,9 @@ namespace corsika { energy * 0.9, // either 10% relative loss max., or get_energy_threshold(vParticle.getPID()) // energy thresholds globally defined for // individual particles - * - 0.99 // need to go 1% below global e-cut to assure removal in ParticleCut. The - // 1% does not matter since at cut-time the entire energy is removed. + * 0.99999 // need to go slightly below global e-cut to assure removal in + // ParticleCut. This does not matter since at cut-time the entire + // energy is removed. ); auto const maxGrammage = (vParticle.getEnergy() - energy_lim) / dEdX; diff --git a/corsika/detail/modules/qgsjetII/Interaction.inl b/corsika/detail/modules/qgsjetII/Interaction.inl index 217fe30b72c3fa27d1ad3794a4a6b1ec8852ffd5..9a86aa880e2333d9247bd2e18d347a83d9184541 100644 --- a/corsika/detail/modules/qgsjetII/Interaction.inl +++ b/corsika/detail/modules/qgsjetII/Interaction.inl @@ -27,20 +27,14 @@ namespace corsika::qgsjetII { - inline Interaction::Interaction(const std::string& dataPath) - : data_path_(dataPath) { - if (dataPath == "") { - if (std::getenv("CORSIKA_DATA")) { - data_path_ = std::string(std::getenv("CORSIKA_DATA")) + "/QGSJetII/"; - CORSIKA_LOG_DEBUG("Searching for QGSJetII data tables in {}", data_path_); - } - } + inline Interaction::Interaction(boost::filesystem::path dataPath) { + CORSIKA_LOG_DEBUG("Reading QGSJetII data tables from {}", dataPath); // initialize QgsjetII static bool initialized = false; if (!initialized) { qgset_(); - datadir DIR(data_path_); + datadir DIR(dataPath.string() + "/"); qgaini_(DIR.data); initialized = true; } diff --git a/corsika/detail/modules/tracking/TrackingLeapFrogCurved.inl b/corsika/detail/modules/tracking/TrackingLeapFrogCurved.inl index 5770fe439dbc309588e990da8669c9f85ceb619c..a6553a71dd3646534645bd8a1ee90c9fa29b08ca 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,23 +57,22 @@ 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(); + auto const& position = particle.getPosition(); CORSIKA_LOG_DEBUG( - "Tracking pid: {}" - " , E = {} GeV", - particle.getPID(), particle.getEnergy() / 1_GeV); - CORSIKA_LOG_DEBUG("Tracking pos: {}", position.getCoordinates()); - CORSIKA_LOG_DEBUG("Tracking E: {} GeV", particle.getEnergy() / 1_GeV); - CORSIKA_LOG_DEBUG("Tracking p: {} GeV", - particle.getMomentum().getComponents() / 1_GeV); - CORSIKA_LOG_DEBUG("Tracking v: {} ", initialVelocity.getComponents()); + "TrackingLeapfrog_Curved pid: {}" + " , E = {} GeV \n" + "\tTracking pos: {} \n" + "\tTracking p: {} GeV \n" + "\tTracking v: {}", + particle.getPID(), particle.getEnergy() / 1_GeV, position.getCoordinates(), + particle.getMomentum().getComponents() / 1_GeV, + initialVelocity.getComponents()); 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,28 @@ 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); } + if (no_deflection) { + CORSIKA_LOG_TRACE("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 +114,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 +137,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 +244,82 @@ 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"); - if (chargeNumber != 0 && abs(plane.getNormal().dot(velocity.cross(magneticfield))) > - 1e-6_T * 1_m / 1_s) { + ElectricChargeType const charge = particle.getCharge(); + + 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> const deltaLs = solve_quadratic_real(denom, p, q); - if (sqrtArg < 0) { + CORSIKA_LOG_TRACE("deltaLs=[{}]", fmt::join(deltaLs, ", ")); + + 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 const& 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 +346,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 61ad3793d0537c47c0efb1b870d11281c663072c..4e7efed775468a6525a236fe7b5a72b45896e518 100644 --- a/corsika/detail/modules/tracking/TrackingLeapFrogStraight.inl +++ b/corsika/detail/modules/tracking/TrackingLeapFrogStraight.inl @@ -29,15 +29,17 @@ namespace corsika { particle.getMomentum() / particle.getEnergy() * constants::c; Point const initialPosition = particle.getPosition(); + CORSIKA_LOG_DEBUG( - "TrackingB pid: {}" - " , E = {} GeV", - particle.getPID(), particle.getEnergy() / 1_GeV); - CORSIKA_LOG_DEBUG("TrackingB pos: {}", initialPosition.getCoordinates()); - CORSIKA_LOG_DEBUG("TrackingB E: {} GeV", particle.getEnergy() / 1_GeV); - CORSIKA_LOG_DEBUG("TrackingB p: {} GeV", - particle.getMomentum().getComponents() / 1_GeV); - CORSIKA_LOG_DEBUG("TrackingB v: {} ", initialVelocity.getComponents()); + "TrackingLeapFrogStraight pid: {}" + " , E = {} GeV \n" + "\tTracking pos: {} \n" + "\tTracking p: {} GeV \n" + "\tTracking v: {} ", + particle.getPID(), particle.getEnergy() / 1_GeV, + initialPosition.getCoordinates(), + particle.getMomentum().getComponents() / 1_GeV, + initialVelocity.getComponents()); typedef decltype(particle.getNode()) node_type; node_type const volumeNode = particle.getNode(); @@ -50,14 +52,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,26 +76,26 @@ 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, // at least if we don't employ concepts as "Helix // Trajectories" or similar - double const maxRadians = 0.01; + double const maxRadians = 0.001; LengthType const steplimit = 2 * cos(maxRadians) * sin(maxRadians) * gyroradius; CORSIKA_LOG_DEBUG("gyroradius {}, Steplimit: {}", gyroradius, steplimit); @@ -111,9 +112,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 9a4659250286414cc88f837e96c3ac5898952006..cad8ac39d99ebf67af35c19eea7974897806b3f9 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 { @@ -28,16 +29,15 @@ namespace corsika::tracking_line { VelocityVector const initialVelocity = particle.getMomentum() / particle.getEnergy() * constants::c; - auto const initialPosition = particle.getPosition(); + auto const& initialPosition = particle.getPosition(); CORSIKA_LOG_DEBUG( - "Tracking pid: {}" - " , E = {} GeV", - particle.getPID(), particle.getEnergy() / 1_GeV); - CORSIKA_LOG_DEBUG("Tracking pos: {}", initialPosition.getCoordinates()); - CORSIKA_LOG_DEBUG("Tracking E: {} GeV", particle.getEnergy() / 1_GeV); - CORSIKA_LOG_DEBUG("Tracking p: {} GeV", - particle.getMomentum().getComponents() / 1_GeV); - CORSIKA_LOG_DEBUG("Tracking v: {} ", initialVelocity.getComponents()); + "TrackingStraight pid: {}" + " , E = {} GeV \n" + "\tTracking pos: {} \n" + "\tTracking p: {} GeV \n" + "\tTracking v: {}", + particle.getPID(), particle.getEnergy() / 1_GeV, initialPosition.getCoordinates(), + particle.getMomentum().getComponents() / 1_GeV, initialVelocity.getComponents()); // traverse the environment volume tree and find next // intersection @@ -51,7 +51,8 @@ namespace corsika::tracking_line { template <typename TParticle> inline Intersections Tracking::intersect(TParticle const& particle, Sphere const& sphere) { - auto const delta = particle.getPosition() - sphere.getCenter(); + auto const& position = particle.getPosition(); + auto const delta = position - sphere.getCenter(); auto const velocity = particle.getMomentum() / particle.getEnergy() * constants::c; auto const vSqNorm = velocity.getSquaredNorm(); auto const R = sphere.getRadius(); @@ -63,6 +64,8 @@ namespace corsika::tracking_line { if (discriminant.magnitude() > 0) { auto const sqDisc = sqrt(discriminant); auto const invDenom = 1 / vSqNorm; + + CORSIKA_LOG_TRACE("numericallyInside={}", sphere.contains(position)); return Intersections((-vDotDelta - sqDisc) * invDenom, (-vDotDelta + sqDisc) * invDenom); } diff --git a/corsika/detail/modules/urqmd/UrQMD.inl b/corsika/detail/modules/urqmd/UrQMD.inl index ee29642e00bbd408c355a5c2c645abef312564ec..f0409e815995f93aedd0175cdd31be5b2a834ae4 100644 --- a/corsika/detail/modules/urqmd/UrQMD.inl +++ b/corsika/detail/modules/urqmd/UrQMD.inl @@ -29,7 +29,7 @@ namespace corsika::urqmd { - inline UrQMD::UrQMD(boost::filesystem::path const& xs_file) { + inline UrQMD::UrQMD(boost::filesystem::path xs_file) { readXSFile(xs_file); ::urqmd::iniurqmdc8_(); } @@ -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); @@ -396,7 +394,7 @@ namespace corsika::urqmd { return mapPDGToUrQMD.at(static_cast<int>(get_PDG(code))); } - inline void UrQMD::readXSFile(boost::filesystem::path const& filename) { + inline void UrQMD::readXSFile(boost::filesystem::path const filename) { boost::filesystem::ifstream file(filename, std::ios::in); if (!file.is_open()) { @@ -431,6 +429,7 @@ namespace corsika::urqmd { std::getline(file, line); } } + file.close(); } } // namespace corsika::urqmd diff --git a/corsika/detail/stack/NuclearStackExtension.inl b/corsika/detail/stack/NuclearStackExtension.inl index 728647ca0bfc5ea89738946ccdef9c36cda31ce2..5dab28ab2506181580fd510e28164c507b70b878 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 7ee12186ace6f189b2c375b8e1a04e502a3cf06f..b9ee5361bdd00301a1ffe2f2c4ecd75563379ec2 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/core/Logging.hpp b/corsika/framework/core/Logging.hpp index 67f059c2fe6d1dc5d4b228e551f4ebbbcd1acf9e..d127040b036dab150ce06a40644050f20a3fab2a 100644 --- a/corsika/framework/core/Logging.hpp +++ b/corsika/framework/core/Logging.hpp @@ -49,7 +49,8 @@ namespace corsika { /* * The default pattern for CORSIKA8 loggers. */ - const std::string default_pattern{"[%n:%^%-8l%$] %v"}; + const std::string minimal_pattern{"[%n:%^%-8l%$] %v"}; + const std::string default_pattern{"[%n:%^%-8l%$(%s:%#)] %v"}; const std::string source_pattern{"[%n:%^%-8l%$(%s:%!:%#)] %v"}; /** @@ -91,7 +92,6 @@ namespace corsika { */ static inline std::shared_ptr<spdlog::logger> corsika_logger = get_logger("corsika", true); - // corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); // many of these free functions are special to the logging // infrastructure so we hide them in the corsika::logging namespace. diff --git a/corsika/framework/geometry/LeapFrogTrajectory.hpp b/corsika/framework/geometry/LeapFrogTrajectory.hpp index 6b67bf6345a5c79400d72d9ec16e13afa380e13f..11e3534a19ae6c8ead194077ae515afb194bbd4c 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/process/ContinuousProcess.hpp b/corsika/framework/process/ContinuousProcess.hpp index c549bc714da8e4c6b655afbb8690568a2f4c4e24..51a7ea7109439401c23e39b722fdd731ff8b1ddb 100644 --- a/corsika/framework/process/ContinuousProcess.hpp +++ b/corsika/framework/process/ContinuousProcess.hpp @@ -18,47 +18,46 @@ namespace corsika { /** - @ingroup Processes - @{ - - Processes with continuous effects along a particle Trajectory - - Create a new ContinuousProcess, e.g. for XYModel, via - @code{.cpp} - class XYModel : public ContinuousProcess<XYModel> {}; - @endcode - - and provide two necessary interface methods: - @code{.cpp} - template <typename TParticle, typename TTrack> - LengthType getMaxStepLength(TParticle const& p, TTrack const& track) const; - @endcode - - which allows any ContinuousProcess to tell to CORSIKA a maximum - allowed step length. Such step-length limitation, if it turns out - to be smaller/sooner than any other limit (decay length, - interaction length, other continuous processes, geometry, etc.) - will lead to a limited step length. - - @code{.cpp} - template <typename TParticle, typename TTrack> - ProcessReturn doContinuous(TParticle& p, TTrack const& t, bool const stepLimit) - const; - @endcode - - which applied any continuous effects on Particle p along - Trajectory t. The particle in all typical scenarios will be - altered by a doContinuous. The flag stepLimit will be true if the - preious evaluation of getMaxStepLength resulted in this - particular ContinuousProcess to be responsible for the step - length limit on the current track t. This information can be - expoited and avoid e.g. any uncessary calculations. - - Particle and Track are the valid classes to - access particles and track (Trajectory) data on the Stack. Those two methods - do not need to be templated, they could use the types - e.g. corsika::setup::Stack::particle_type -- but by the cost of - loosing all flexibility otherwise provided. + * @ingroup Processes + * @{ + * Processes with continuous effects along a particle Trajectory. + * + * Create a new ContinuousProcess, e.g. for XYModel, via: + * @code{.cpp} + * class XYModel : public ContinuousProcess<XYModel> {}; + * @endcode + * + * and provide two necessary interface methods: + * @code{.cpp} + * template <typename TParticle, typename TTrack> + * LengthType getMaxStepLength(TParticle const& p, TTrack const& track) const; + * @endcode + + * which allows any ContinuousProcess to tell to CORSIKA a maximum + * allowed step length. Such step-length limitation, if it turns out + * to be smaller/sooner than any other limit (decay length, + * interaction length, other continuous processes, geometry, etc.) + * will lead to a limited step length. + + * @code{.cpp} + * template <typename TParticle, typename TTrack> + * ProcessReturn doContinuous(TParticle& p, TTrack const& t, bool const stepLimit) + * const; + * @endcode + + * which applied any continuous effects on Particle p along + * Trajectory t. The particle in all typical scenarios will be + * altered by a doContinuous. The flag stepLimit will be true if the + * preious evaluation of getMaxStepLength resulted in this + * particular ContinuousProcess to be responsible for the step + * length limit on the current track t. This information can be + * expoited and avoid e.g. any uncessary calculations. + + * Particle and Track are the valid classes to + * access particles and track (Trajectory) data on the Stack. Those two methods + * do not need to be templated, they could use the types + * e.g. corsika::setup::Stack::particle_type -- but by the cost of + * loosing all flexibility otherwise provided. */ @@ -68,8 +67,8 @@ namespace corsika { }; /** - * ProcessTraits specialization to flag ContinuousProcess objects - **/ + * ProcessTraits specialization to flag ContinuousProcess objects. + */ template <typename TProcess> struct is_continuous_process< TProcess, std::enable_if_t< diff --git a/corsika/framework/process/ContinuousProcessIndex.hpp b/corsika/framework/process/ContinuousProcessIndex.hpp index e36bf2812a33c6f03e4058033585f846a871b897..7192b86bf968ffdc74b5420940ea549befa12c73 100644 --- a/corsika/framework/process/ContinuousProcessIndex.hpp +++ b/corsika/framework/process/ContinuousProcessIndex.hpp @@ -11,12 +11,10 @@ namespace corsika { /** - @ingroup Processes - - To index individual processes (continuous processes) inside a - ProcessSequence. - - **/ + * @ingroup Processes + * To index individual processes (continuous processes) inside a + * ProcessSequence. + */ class ContinuousProcessIndex { public: diff --git a/corsika/framework/process/ContinuousProcessStepLength.hpp b/corsika/framework/process/ContinuousProcessStepLength.hpp index 575be9063d49a32b5853320dc9d25bac40a7b9d8..55c2f417a470b8e4a475de8269c5e19fe8247ff5 100644 --- a/corsika/framework/process/ContinuousProcessStepLength.hpp +++ b/corsika/framework/process/ContinuousProcessStepLength.hpp @@ -14,12 +14,10 @@ namespace corsika { /** - @ingroup Processes - - To store step length in LengthType and unique index in ProcessSequence of shortest - step ContinuousProcess. - - **/ + * @ingroup Processes + * To store step length in LengthType and unique index in ProcessSequence of shortest + * step ContinuousProcess. + */ class ContinuousProcessStepLength { public: diff --git a/corsika/framework/utility/CubicSolver.hpp b/corsika/framework/utility/CubicSolver.hpp new file mode 100644 index 0000000000000000000000000000000000000000..0959a4fa84c3bee6aad856ccb57c887d21e30c9c --- /dev/null +++ b/corsika/framework/utility/CubicSolver.hpp @@ -0,0 +1,93 @@ +/* + * (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); + + /** + * Cubic function. + * + * T must be a floating point type. + * + * @return a x^3 + b x^2 + c x + d + */ + template <typename T> + T cubic_function(T x, T a, T b, T c, T d); + + /** + @return 3 a x^2 + 2 b x + c + */ + template <typename T> + T cubic_function_dfdx(T x, T a, T b, T c); + + /** + @return 6 a x + 2 b + */ + template <typename T> + T cubic_function_d2fd2x(T x, T a, T 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 0000000000000000000000000000000000000000..754039571287a1a816c77ec741b7d850b87c46da --- /dev/null +++ b/corsika/framework/utility/LinearSolver.hpp @@ -0,0 +1,23 @@ +/* + * (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); + + std::vector<std::complex<double>> solve_linear(double a, double b); + +} // 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 0000000000000000000000000000000000000000..968c01bf13e3680b91b27005084c909b4e473ea7 --- /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 ad9484939d10691e38c7e74d9e2632b3d2e60463..d6e20f02fc3d6d594a64475d9ce1117b4dec159c 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 8276c0d1235f44539bfc17468f20aff5eda902b5..f7ccba999c531333dbe8dedf9b7550c97f09ba19 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/modules/TrackWriter.hpp b/corsika/modules/TrackWriter.hpp index 5bf9856b389fe80774ea629b27a4b4dc10f33c85..8b9b0efe7faca74ade9f9c55ea463186bc111e98 100644 --- a/corsika/modules/TrackWriter.hpp +++ b/corsika/modules/TrackWriter.hpp @@ -20,6 +20,7 @@ namespace corsika { public: TrackWriter(std::string const& filename); + ~TrackWriter(); template <typename TParticle, typename TTrack> ProcessReturn doContinuous(TParticle const&, TTrack const&, bool const limitFlag); diff --git a/corsika/modules/qgsjetII/Interaction.hpp b/corsika/modules/qgsjetII/Interaction.hpp index a45282f58ae3073237adf19a3c0c739d6153047c..63c878df5d4cea4feadbe2ff028760a79643e741 100644 --- a/corsika/modules/qgsjetII/Interaction.hpp +++ b/corsika/modules/qgsjetII/Interaction.hpp @@ -13,6 +13,10 @@ #include <corsika/framework/random/RNGManager.hpp> #include <corsika/framework/process/InteractionProcess.hpp> #include <corsika/modules/qgsjetII/ParticleConversion.hpp> +#include <corsika/framework/utility/CorsikaData.hpp> + +#include <boost/filesystem/path.hpp> + #include <qgsjet-II-04.hpp> #include <string> @@ -21,14 +25,8 @@ namespace corsika::qgsjetII { class Interaction : public corsika::InteractionProcess<Interaction> { - std::string data_path_; - int count_ = 0; - bool initialized_ = false; - QgsjetIIHadronType alternate_ = - QgsjetIIHadronType::PiPlusType; // for pi0, rho0 projectiles - public: - Interaction(const std::string& dataPath = ""); + Interaction(boost::filesystem::path dataPath = corsika_data("QGSJetII")); ~Interaction(); bool wasInitialized() { return initialized_; } @@ -53,6 +51,11 @@ namespace corsika::qgsjetII { void doInteraction(TSecondaryView&); private: + int count_ = 0; + bool initialized_ = false; + QgsjetIIHadronType alternate_ = + QgsjetIIHadronType::PiPlusType; // for pi0, rho0 projectiles + corsika::default_prng_type& rng_ = corsika::RNGManager::getInstance().getRandomStream("qgsjet"); const int maxMassNumber_ = 208; diff --git a/corsika/modules/urqmd/UrQMD.hpp b/corsika/modules/urqmd/UrQMD.hpp index 3f6d3e9936f1fd13fcb831359f8faee6768f2948..69cb4a93750b3389749c279cdc0b6f8f84765ad5 100644 --- a/corsika/modules/urqmd/UrQMD.hpp +++ b/corsika/modules/urqmd/UrQMD.hpp @@ -27,7 +27,7 @@ namespace corsika::urqmd { class UrQMD : public InteractionProcess<UrQMD> { public: - UrQMD(boost::filesystem::path const& path = corsika_data("UrQMD/UrQMD-1.3.1-xs.dat")); + UrQMD(boost::filesystem::path const path = corsika_data("UrQMD/UrQMD-1.3.1-xs.dat")); template <typename TParticle> GrammageType getInteractionLength(TParticle const&) const; @@ -46,7 +46,7 @@ namespace corsika::urqmd { private: static CrossSectionType getCrossSection(Code, Code, HEPEnergyType, int); - void readXSFile(boost::filesystem::path const&); + void readXSFile(boost::filesystem::path); // data members default_prng_type& RNG_ = RNGManager::getInstance().getRandomStream("urqmd"); diff --git a/corsika/stack/NuclearStackExtension.hpp b/corsika/stack/NuclearStackExtension.hpp index 34bd1377d99a5167b2ac406e506a4c8492173bc3..19e9a44c32aa401147d57585c89a7c1ab3ec6f4e 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 adaae996b63a5b69efa85dc8ad8956e253de44b9..e5a765781ae44f38b38da7d4135a6363c697f5d3 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/do-clang-format.py b/do-clang-format.py index d6ea597535e69da32de80ff31962ba998523305d..fcfe674116832aea4ab94eb0c930b5d5c512fced 100755 --- a/do-clang-format.py +++ b/do-clang-format.py @@ -10,6 +10,7 @@ import os import sys import re +debug = False do_progress = False try: from progress.bar import ChargingBar @@ -77,6 +78,9 @@ else: filelist_clean.append(f) filelist = filelist_clean +if debug: + print ("filelist: ", filelist) + cmd = "clang-format" if "CLANG_FORMAT" in os.environ: cmd = os.environ["CLANG_FORMAT"] @@ -99,11 +103,18 @@ if do_progress: bar = ChargingBar('Processing', max=len(filelist)) if args.apply: + changed = [] for filename in filelist: if bar: bar.next() + a = open(filename, "rb").read() subp.check_call(cmd.split() + ["-i", filename]) + b = open(filename, "rb").read() + if a != b: + changed.append(filename) if bar: bar.finish() - + if debug: + print ("changed: ", changed) + else: # only print files which need formatting files_need_formatting = 0 diff --git a/modules/qgsjetII/qgsjet-II-04.cpp b/modules/qgsjetII/qgsjet-II-04.cpp index eea2724a88c07f1b028aba0703173a1ebc1f0504..8ffd24df4bd7b860116b7792e16671562af051b6 100644 --- a/modules/qgsjetII/qgsjet-II-04.cpp +++ b/modules/qgsjetII/qgsjet-II-04.cpp @@ -2,7 +2,7 @@ #include <iostream> -datadir::datadir(const std::string& dir) { +datadir::datadir(std::string const& dir) { if (dir.length() > 130) { std::cerr << "QGSJetII error, will cut datadir \"" << dir << "\" to 130 characters: " << std::endl; @@ -13,11 +13,9 @@ datadir::datadir(const std::string& dir) { data[i + 1] = '\0'; } - /** @function qgran link to random number generation */ -double qgran_(int&) { return qgsjetII::rndm_interface(); } - +double qgran_(int&) { return qgsjetII::rndm_interface(); } diff --git a/src/framework/core/CMakeLists.txt b/src/framework/core/CMakeLists.txt index 558b227f68801ec98c794c832ae5d8e654c1a703..f0edcdebd80fd61962c258b8e8ab43963030e5f6 100644 --- a/src/framework/core/CMakeLists.txt +++ b/src/framework/core/CMakeLists.txt @@ -5,7 +5,7 @@ file (MAKE_DIRECTORY ${output_dir}) add_custom_command ( OUTPUT ${output_dir}/GeneratedParticleProperties.inc - OUTPUT ${output_dir}/GeneratedParticleClasses.inc + ${output_dir}/GeneratedParticleClasses.inc ${output_dir}/particle_db.pkl COMMAND ${input_dir}/pdxml_reader.py ${input_dir}/ParticleData.xml ${input_dir}/NuclearData.xml @@ -22,7 +22,9 @@ add_custom_command ( add_custom_target (GenParticlesHeaders DEPENDS ${output_dir}/GeneratedParticleProperties.inc - ${output_dir}/GeneratedParticleClasses.inc) + ${output_dir}/GeneratedParticleClasses.inc + ${output_dir}/particle_db.pkl + ) add_dependencies (CORSIKA8 GenParticlesHeaders) install ( diff --git a/src/modules/qgsjetII/CMakeLists.txt b/src/modules/qgsjetII/CMakeLists.txt index 36ebe9b693b927de9acfca03be3dc016440c4050..9b9777356e511db01238c2a68389c5c476e738a7 100644 --- a/src/modules/qgsjetII/CMakeLists.txt +++ b/src/modules/qgsjetII/CMakeLists.txt @@ -10,7 +10,7 @@ add_custom_command ( ${input_dir}/qgsjet-II-04-codes.dat DEPENDS ${input_dir}/code_generator.py ${input_dir}/qgsjet-II-04-codes.dat - GenParticlesHeaders # for particle_db.pkl + ${PROJECT_BINARY_DIR}/corsika/framework/core/particle_db.pkl WORKING_DIRECTORY ${output_dir} COMMENT "Generate conversion tables for particle codes QGSJetII <-> CORSIKA" diff --git a/src/modules/sibyll/CMakeLists.txt b/src/modules/sibyll/CMakeLists.txt index b3d97a12349a5a5f2cbd9917404a8685894f0096..969d2f1d2e0e1bada1a4c247901ef381c121d368 100644 --- a/src/modules/sibyll/CMakeLists.txt +++ b/src/modules/sibyll/CMakeLists.txt @@ -10,7 +10,7 @@ add_custom_command ( ${input_dir}/sibyll_codes.dat DEPENDS ${input_dir}/code_generator.py ${input_dir}/sibyll_codes.dat - GenParticlesHeaders # for particle_db.pkl + ${PROJECT_BINARY_DIR}/corsika/framework/core/particle_db.pkl WORKING_DIRECTORY ${output_dir}/ COMMENT "Generate conversion tables for particle codes SIBYLL <-> CORSIKA" diff --git a/tests/framework/CMakeLists.txt b/tests/framework/CMakeLists.txt index 35f61737b13dfedef24d6746cf92e56a26658ebe..829709779bd7ec19b8b9fb0037422a1f6ca105da 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 caf6f08965bf4468294df23dee13e4f265dd4c47..d092a4b61ea46dfbcb734a4c8f31013550451204 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 b860e9955d3c3122c4295cdaa6f32875f9aa6021..3e930911b095699bd09a10813c38c167c56b5dcc 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 d51a56b1a254146847f742bfc0283ee9f6e44be2..b88d2fc97a0f2c99e5100268e168b6134598ca63 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 1f76fafc21927832063f5271fb8ed26443dffa50..de6defc6ad7a0f94fcbcc289a012de38761112dd 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 2b6cdb58034d06c82cf336c2826cdb2e4f4248f6..030d33dfac78b92464b677b489f7f62b8fb6fa4c 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 09b458295ef0b118684ed2707389a0a5f0fcfd9c..489d6261b17d6c68f85a01dae591992cbbba6a90 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 46d97f710f5793a5f27bbc4ed255c49cbdad627b..1328ea7badbc86f13809197a06ca773593381137 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 cbe997d203a02705001f9304bc767cfedce848db..042e4212f122bb538c86e9c78317fb86259b3ba8 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 556d4733b60b82bf295ff47463a24d3775c48553..4b4b7ff25dca0ba6a5cf44018c9efde66a150e82 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 b737d7dfa778b0cdf78b235479177e51534311b2..bf92cf8df00e8f12d64c0d5779f59b7976c4e09d 100644 --- a/tests/framework/testLogging.cpp +++ b/tests/framework/testLogging.cpp @@ -14,8 +14,6 @@ using namespace corsika; TEST_CASE("Logging", "[Logging]") { - 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!"); logging::warn("(1) This is a warning message!"); diff --git a/tests/framework/testNullModel.cpp b/tests/framework/testNullModel.cpp index 5b1f351e13638a1ac2358bd5f0b9d2f295563350..872fc34dbbeb685db1806c74c93f4709443db545 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 b74d836bc47d5715ec4f2504b30fb056af545af4..d8c71e6e787ce465d367c29dfa5d06b0b2a51faf 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 5774575e6844754289cdcd2130313d3b03d95359..2d0c0720ae163f2777c80837d8179b9355aba044 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 d5c16438a2ab6de1595393205d4eff4320a43d08..89182311a650e76968226e3dc2cf5a97c8b4e29a 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 87cfd91c0c74cb3b9c38cc8bda31af7a96e52c8c..fa113f88b6e111e9837d9ef96d28a432024974dd 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 0000000000000000000000000000000000000000..52f1fa13fc29f59d7b90e4471b76bb51997b0613 --- /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 b37dc495d7b03850ce135dd87942f8227531963d..4171647af5a547baeb55e6a2e3c3701eaeb77b29 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 8d15544aca0d5c2dc1923af5853e6de7cafeb4f4..5c90aa9cd6ad65500a13a178d7dd6b0e52355006 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 34718d69bb94e93c7e032e42df8f7e8e9c541f6e..2128e0a47a14141bcede691721a6dfea2da32d68 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 68a85d81b2af7343ed323777daef39f55286a69e..4ebc03342914dc28e9137e7fd96dde010e8ac544 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 a24433b080b4272955f01dfb2d116b43e0d5a2d6..6485b7df95a804e11301c0bc01095d705586fdea 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 eccf73f287e9b07642ba5ed65e807d15d524f935..e28c5ffcd03a2d9eba64a890f5009ba9c9f18fb3 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/testParticleCut.cpp b/tests/modules/testParticleCut.cpp index 1bc67084bc633c62539ec8a644eb42528a8e4106..0a2388772f960667cf99f7431089e5938ae1f66b 100644 --- a/tests/modules/testParticleCut.cpp +++ b/tests/modules/testParticleCut.cpp @@ -26,7 +26,6 @@ using namespace corsika; TEST_CASE("ParticleCut", "processes") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] %v"); feenableexcept(FE_INVALID); using EnvType = setup::Environment; diff --git a/tests/modules/testPythia8.cpp b/tests/modules/testPythia8.cpp index 8e178affc3f7c26c5a34df8caf2777386dba8d24..a4a93126a6c7456608f0cbd3f126e72910ae876d 100644 --- a/tests/modules/testPythia8.cpp +++ b/tests/modules/testPythia8.cpp @@ -23,7 +23,6 @@ using namespace corsika; TEST_CASE("Pythia", "[processes]") { logging::set_level(logging::level::info); - corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); SECTION("linking pythia") { using namespace Pythia8; @@ -95,7 +94,6 @@ auto sumMomentum(TStackView const& view, CoordinateSystemPtr const& vCS) { TEST_CASE("PythiaInterface", "[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::Proton); auto const& cs = *csPtr; diff --git a/tests/modules/testQGSJetII.cpp b/tests/modules/testQGSJetII.cpp index 4a604fd8bbf32f440db2eb0e073aed684b412fe4..3d68eb0cd4d14cd07c09bfd9bbc8afc0a2116350 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(100)); // 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 @@ -219,6 +216,7 @@ TEST_CASE("QgsjetIIInterface", "[processes]") { auto [stackPtr, secViewPtr] = setup::testing::setup_stack( Code::Electron, 0, 0, 100_GeV, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); + [[maybe_unused]] setup::StackView& view = *(secViewPtr.get()); auto particle = stackPtr->first(); corsika::qgsjetII::Interaction model; GrammageType const length = model.getInteractionLength(particle); @@ -228,7 +226,8 @@ TEST_CASE("QgsjetIIInterface", "[processes]") { auto [stackPtr, secViewPtr] = setup::testing::setup_stack( Code::Pi0, 0, 0, 1000_GeV, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); - setup::StackView& view = *(secViewPtr.get()); + [[maybe_unused]] setup::StackView& view = *(secViewPtr.get()); + [[maybe_unused]] auto particle = stackPtr->first(); corsika::qgsjetII::Interaction model; model.doInteraction(view); CHECK(view.getSize() == Approx(18).margin(2)); // this is not physics validation @@ -237,28 +236,31 @@ TEST_CASE("QgsjetIIInterface", "[processes]") { auto [stackPtr, secViewPtr] = setup::testing::setup_stack( Code::Rho0, 0, 0, 1000_GeV, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); - setup::StackView& view = *(secViewPtr.get()); + [[maybe_unused]] setup::StackView& view = *(secViewPtr.get()); + [[maybe_unused]] auto particle = stackPtr->first(); 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(8)); // this is not physics validation } { // Lambda is internally converted into neutron auto [stackPtr, secViewPtr] = setup::testing::setup_stack( Code::Lambda0, 0, 0, 100_GeV, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); - setup::StackView& view = *(secViewPtr.get()); + [[maybe_unused]] setup::StackView& view = *(secViewPtr.get()); + [[maybe_unused]] auto particle = stackPtr->first(); corsika::qgsjetII::Interaction model; model.doInteraction(view); - CHECK(view.getSize() == Approx(25).margin(3)); // this is not physics validation + CHECK(view.getSize() == Approx(15).margin(10)); // 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()); + [[maybe_unused]] setup::StackView& view = *(secViewPtr.get()); + [[maybe_unused]] auto particle = stackPtr->first(); corsika::qgsjetII::Interaction model; model.doInteraction(view); - CHECK(view.getSize() == Approx(25).margin(3)); // this is not physics validation + CHECK(view.getSize() == Approx(40).margin(20)); // this is not physics validation } } } diff --git a/tests/modules/testSibyll.cpp b/tests/modules/testSibyll.cpp index dc237b4a379613dff02be6802ca7047776aa45d4..dc5249a7218e2e2dea96b7a23f791833ef4e8c9a 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 00187ed9addbf6076641540dd494beccbd85758b..0b2a3ab7b9a489df955f00814ce371dfc4a026e9 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 87cd51e93baf36dd89430c230b0d97e9c5ee9357..b8574124227f477d5b2dfda969b10c4110585e95 100644 --- a/tests/modules/testTracking.cpp +++ b/tests/modules/testTracking.cpp @@ -20,32 +20,52 @@ #include <catch2/catch.hpp> +#include <boost/type_index.hpp> + using namespace corsika; +struct NonExistingDummyObject : public IVolume { + NonExistingDummyObject const& getVolume() const { return *this; } + bool contains(Point const&) const { return false; } +}; + template <typename T> int sgn(T val) { return (T(0) < val) - (val < T(0)); } /** - \file testTracking.cpp + @file testTracking.cpp - This is the unified and commond unit test for all Tracking algorithms: + This is the unified and common unit test for all Tracking algorithms: - tracking_leapfrog_curved::Tracking - tracking_leapfrog_straight::Tracking - tracking_line::Tracking + + The main part of tests are to inject particles at 10GeV momentum at + (-Rg,0,0) in +x direction into a sphere of radius Rg, where Rg is + the gyroradius (or 10m for neutral particles). Then it is checked + where the particles leaves the sphere for different charges + (-1,0,+1) and field strength (-50uT, 0T, +50uT). + + Each test is perfromed once, with the particles starting logically + outside of the Rg sphere (thus it first has to enter insides) and a + second time with the particle already logically inside the sphere. + + There is a second smaller, internal sphere at +z displacement. Only + neutral particles are allowed and expected to hit this. + + All those tests are parameterized, thus, they can be easily extended + or applied to new algorithms. + */ -TEMPLATE_TEST_CASE("TrackingLeapfrog_Curved", "tracking", - tracking_leapfrog_curved::Tracking, +TEMPLATE_TEST_CASE("Tracking", "tracking", tracking_leapfrog_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; @@ -69,17 +89,19 @@ TEMPLATE_TEST_CASE("TrackingLeapfrog_Curved", "tracking", Bfield / 1_uT, outer)) { CORSIKA_LOG_DEBUG( - "********************\n TEST section PID={}, Bfield={} " - "uT, outer (?)={}", - PID, Bfield / 1_uT, outer); + "********************\n TEST algo={} section PID={}, " + "Bfield={} " + "uT, start_outside={}", + boost::typeindex::type_id<TestType>().pretty_name(), PID, Bfield / 1_uT, outer); const int chargeNumber = get_charge_number(PID); LengthType radius = 10_m; 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; } @@ -91,6 +113,12 @@ TEMPLATE_TEST_CASE("TrackingLeapfrog_Curved", "tracking", TestType tracking; Point const center(cs, {0_m, 0_m, 0_m}); auto target = setup::Environment::createNode<Sphere>(center, radius); + // every particle should hit target_2 + auto target_2 = setup::Environment::createNode<Sphere>( + Point(cs, {-radius * 3 / 4, 0_m, 0_m}), radius * 0.2); + // only neutral particles hit_target_neutral + auto target_neutral = setup::Environment::createNode<Sphere>( + Point(cs, {radius / 2, 0_m, 0_m}), radius * 0.1); using MyHomogeneousModel = MediumPropertyModel< UniformMagneticField<HomogeneousMedium<setup::EnvironmentInterface>>>; @@ -99,8 +127,18 @@ TEMPLATE_TEST_CASE("TrackingLeapfrog_Curved", "tracking", target->setModelProperties<MyHomogeneousModel>( Medium::AirDry1Atm, magneticfield, 1_g / (1_m * 1_m * 1_m), NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<float>{1.})); + target_neutral->setModelProperties<MyHomogeneousModel>( + Medium::AirDry1Atm, magneticfield, 1_g / (1_m * 1_m * 1_m), + NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<float>{1.})); + target_2->setModelProperties<MyHomogeneousModel>( + Medium::AirDry1Atm, magneticfield, 1_g / (1_m * 1_m * 1_m), + NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<float>{1.})); auto* targetPtr = target.get(); + auto* targetPtr_2 = target_2.get(); + auto* targetPtr_neutral = target_neutral.get(); worldPtr->addChild(std::move(target)); + targetPtr->addChild(std::move(target_2)); + targetPtr->addChild(std::move(target_neutral)); auto [stack, viewPtr] = setup::testing::setup_stack(PID, 0, 0, P0, targetPtr, cs); { [[maybe_unused]] auto& viewPtr_dum = viewPtr; } @@ -123,18 +161,29 @@ 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 - while (nextVol == targetPtr) { + bool hit_neutral = false; + bool hit_2nd = false; + while (nextVol != worldPtr) { + if (nextVol == targetPtr_neutral) hit_neutral = true; + if (nextVol == targetPtr_2) hit_2nd = true; const auto [traj2, nextVol2] = tracking.getTrack(particle); nextVol = nextVol2; particle.setNode(nextVol); particle.setPosition(traj2.getPosition(1)); particle.setMomentum(traj2.getDirection(1) * particle.getMomentum().getNorm()); + CORSIKA_LOG_TRACE("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); + CHECK(hit_2nd == true); + CHECK(hit_neutral == (deflect == 0 ? true : false)); Point pointCheck(cs, (deflect == 0 ? radius : 0_m), (deflect * radius), 0_m); @@ -147,3 +196,166 @@ TEMPLATE_TEST_CASE("TrackingLeapfrog_Curved", "tracking", Approx(0).margin(1e-3)); } } + +/** specifc test for curved leap-frog algorithm **/ + +TEST_CASE("TrackingLeapFrogCurved") { + + logging::set_level(logging::level::info); + + const HEPEnergyType P0 = 10_GeV; + + corsika::Code PID = Code::MuPlus; + + using MyHomogeneousModel = MediumPropertyModel< + UniformMagneticField<HomogeneousMedium<setup::EnvironmentInterface>>>; + + SECTION("infinite sphere / universe") { + + auto [env, csPtr, worldPtr] = + corsika::setup::testing::setup_environment(Code::Oxygen, 100_uT); + { [[maybe_unused]] const auto& env_dummy = env; } + auto const& cs = *csPtr; + + tracking_leapfrog_curved::Tracking tracking; + Point const center(cs, {0_m, 0_m, 0_m}); + + auto [stack, viewPtr] = setup::testing::setup_stack(PID, 0, 0, P0, worldPtr, cs); + { [[maybe_unused]] auto& viewPtr_dum = viewPtr; } + auto particle = stack->first(); + // Note: momentum in X-direction + // magnetic field in X-direction + + particle.setPosition(Point(cs, 0_m, 0_m, 0_m)); + + Sphere sphere(center, 1_km * std::numeric_limits<double>::infinity()); + + auto intersections = tracking.intersect(particle, sphere); + // this must be a "linear trajectory" with no curvature + + CHECK(intersections.hasIntersections() == false); + } + + SECTION("momentum along field") { + + auto [env, csPtr, worldPtr] = + corsika::setup::testing::setup_environment(Code::Oxygen, 100_uT); + { [[maybe_unused]] const auto& env_dummy = env; } + auto const& cs = *csPtr; + + tracking_leapfrog_curved::Tracking tracking; + Point const center(cs, {0_m, 0_m, 0_m}); + auto target = setup::Environment::createNode<Sphere>(center, 10_km); + + MagneticFieldVector magneticfield(cs, 100_T, 0_T, 0_uT); + target->setModelProperties<MyHomogeneousModel>( + Medium::AirDry1Atm, magneticfield, 1_g / (1_m * 1_m * 1_m), + NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<float>{1.})); + auto* targetPtr = target.get(); + worldPtr->addChild(std::move(target)); + + auto [stack, viewPtr] = setup::testing::setup_stack(PID, 0, 0, P0, targetPtr, cs); + { [[maybe_unused]] auto& viewPtr_dum = viewPtr; } // prevent warning + auto particle = stack->first(); + // Note: momentum in X-direction + // magnetic field in X-direction + + particle.setNode(targetPtr); // set particle outside "target" volume + particle.setPosition(Point(cs, 0_m, 0_m, 0_m)); + + auto [traj, nextVol] = tracking.getTrack(particle); + { [[maybe_unused]] auto const& dummy = nextVol; } // prevent warning + + // this must be a "linear trajectory" with no curvature + CHECK(traj.getDirection(0).getComponents() == traj.getDirection(1).getComponents()); + } +} + +TEMPLATE_TEST_CASE("TrackingFail", "doesntwork", tracking_leapfrog_curved::Tracking, + tracking_leapfrog_straight::Tracking, tracking_line::Tracking) { + + logging::set_level(logging::level::info); + + const HEPEnergyType P0 = 10_GeV; + + auto [env, csPtr, worldPtr] = + corsika::setup::testing::setup_environment(Code::Oxygen, 1_mT); + { [[maybe_unused]] const auto& env_dummy = env; } + auto const& cs = *csPtr; + + TestType tracking; + Point const center(cs, {0_m, 0_m, 0_m}); + + auto [stack, viewPtr] = + setup::testing::setup_stack(Code::Proton, 0, 0, P0, worldPtr, cs); + { [[maybe_unused]] auto& viewPtr_dum = viewPtr; } + auto particle = stack->first(); + NonExistingDummyObject const dummy; + CHECK_THROWS(tracking.intersect(particle, dummy)); +} + +TEMPLATE_TEST_CASE("TrackingPlane", "plane", tracking_leapfrog_curved::Tracking, + tracking_leapfrog_straight::Tracking, tracking_line::Tracking) { + + logging::set_level(logging::level::trace); + + const HEPEnergyType P0 = 10_GeV; + + auto PID = GENERATE(as<Code>{}, Code::MuPlus, Code::MuPlus, Code::Gamma); + // for algorithms that know magnetic deflections choose: +-50uT, 0uT + // otherwise just 0uT + auto Bfield = GENERATE(filter( + []([[maybe_unused]] MagneticFluxType v) { + if constexpr (std::is_same_v<TestType, tracking_line::Tracking>) + return v == 0_uT; + else + return true; + }, + values<MagneticFluxType>({50_uT, 0_uT, -50_uT}))); + + SECTION(fmt::format("Tracking PID={}, Bfield={} uT", PID, Bfield / 1_uT)) { + + CORSIKA_LOG_DEBUG( + "********************\n TEST algo={} section PID={}, " + "Bfield={}uT ", + boost::typeindex::type_id<TestType>().pretty_name(), PID, Bfield / 1_uT); + + const int chargeNumber = get_charge_number(PID); + LengthType radius = 10_m; + int deflect = 0; + if (chargeNumber != 0 and Bfield != 0_T) { + deflect = 1; + 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; + } + + auto [env, csPtr, worldPtr] = + corsika::setup::testing::setup_environment(Code::Oxygen, Bfield); + { [[maybe_unused]] const auto& env_dummy = env; } + auto const& cs = *csPtr; + + TestType tracking; + Point const center(cs, {0_m, 0_m, 0_m}); + + auto [stack, viewPtr] = setup::testing::setup_stack(PID, 0, 0, P0, worldPtr, cs); + { [[maybe_unused]] auto& viewPtr_dum = viewPtr; } + auto particle = stack->first(); + // Note: momentum in X-direction + // magnetic field in Z-direction + + particle.setPosition(Point(cs, -radius, 0_m, 0_m)); + + // put plane where we expect deflection by radius/2 + Plane const plane(Point(cs, radius * (1 - sqrt(3. / 4.)), 0_m, 0_m), + DirectionVector(cs, {-1., 0., 0.})); + Intersections const hit = tracking.intersect(particle, plane); + + CORSIKA_LOG_DEBUG("entry={} exit={}", hit.getEntry(), hit.getExit()); + + CHECK(hit.hasIntersections()); + CHECK(hit.getEntry() / 1_s == Approx(0.00275 * deflect).margin(0.0003)); + CHECK(hit.getExit() == 1_s * std::numeric_limits<double>::infinity()); + } +} diff --git a/tests/modules/testUrQMD.cpp b/tests/modules/testUrQMD.cpp index 0f9f4a86243ec0f7f5b0c16e989b9459a5e0a293..f55aa032948c22026b170776d8946dc074c6e688 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)); @@ -99,6 +98,7 @@ TEST_CASE("UrQMD") { { [[maybe_unused]] auto const& env_dummy = env; } auto [stack, view] = setup::testing::setup_stack(Code::Proton, 0, 0, 100_GeV, nodePtr, cs); + [[maybe_unused]] setup::StackView& viewRef = *(view.get()); CHECK(urqmd.getInteractionLength(stack->getNextParticle()) / 1_g * square(1_cm) == Approx(105).margin(5)); } @@ -109,6 +109,7 @@ TEST_CASE("UrQMD") { { [[maybe_unused]] auto const& env_dummy = env; } auto [stack, view] = setup::testing::setup_stack(Code::Neutron, 0, 0, 100_GeV, nodePtr, cs); + [[maybe_unused]] setup::StackView& viewRef = *(view.get()); CHECK_THROWS(urqmd.getInteractionLength(stack->getNextParticle())); } @@ -121,6 +122,7 @@ TEST_CASE("UrQMD") { auto [stackPtr, secViewPtr] = setup::testing::setup_stack( Code::Nucleus, A, Z, 40_GeV, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); + [[maybe_unused]] setup::StackView& viewRef = *(secViewPtr.get()); CHECK(stackPtr->getEntries() == 1); CHECK(secViewPtr->getEntries() == 0); @@ -172,6 +174,7 @@ TEST_CASE("UrQMD") { auto [stackPtr, secViewPtr] = setup::testing::setup_stack( Code::PiPlus, 0, 0, 40_GeV, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr); + [[maybe_unused]] auto particle = stackPtr->first(); CHECK_THROWS(urqmd.doInteraction(*secViewPtr)); // Code::Proton not a valid target } @@ -227,22 +230,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)); - } }