diff --git a/corsika/detail/modules/sophia/InteractionModel.inl b/corsika/detail/modules/sophia/InteractionModel.inl new file mode 100644 index 0000000000000000000000000000000000000000..89560a3929451e4716bf5e9604a0df2510ad5764 --- /dev/null +++ b/corsika/detail/modules/sophia/InteractionModel.inl @@ -0,0 +1,142 @@ +/* + * (c) Copyright 2022 CORSIKA Project, corsika-project@lists.kit.edu + * + * This software is distributed under the terms of the GNU General Public + * Licence version 3 (GPL Version 3). See file LICENSE for a full version of + * the license. + */ + +#pragma once + +#include <corsika/framework/geometry/Point.hpp> + +#include <corsika/modules/sophia/ParticleConversion.hpp> +#include <corsika/framework/utility/COMBoost.hpp> +#include <corsika/modules/sophia/SophiaStack.hpp> +#include <corsika/framework/core/EnergyMomentumOperations.hpp> + +#include <sophia.hpp> + +namespace corsika::sophia { + + inline void InteractionModel::setVerbose(bool const flag) { sophia_listing_ = flag; } + + inline InteractionModel::InteractionModel() + : sophia_listing_(false) { + // set all particles stable in SOPHIA + for (int i = 0; i < 49; ++i) so_csydec_.idb[i] = -abs(so_csydec_.idb[i]); + }; + + inline InteractionModel::~InteractionModel() { + CORSIKA_LOG_DEBUG("Sophia::Model n={}", count_); + } + + inline bool constexpr InteractionModel::isValid(Code const projectileId, + Code const targetId, + HEPEnergyType const sqrtSnn) const { + if ((minEnergyCoM_ > sqrtSnn) || (sqrtSnn > maxEnergyCoM_)) { return false; } + + if (!(targetId == Code::Proton || targetId == Code::Neutron || + targetId == Code::Hydrogen)) + return false; + + if (projectileId != Code::Photon) return false; + + return true; + } + + template <typename TSecondaryView> + inline void InteractionModel::doInteraction(TSecondaryView& secondaries, + Code const projectileId, + Code const targetId, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) { + + CORSIKA_LOGGER_DEBUG(logger_, "projectile: Id={}, E={} GeV, p3={} GeV", projectileId, + projectileP4.getTimeLikeComponent() / 1_GeV, + projectileP4.getSpaceLikeComponents().getComponents() / 1_GeV); + CORSIKA_LOGGER_DEBUG(logger_, "target: Id={}, E={} GeV, p3={} GeV", targetId, + targetP4.getTimeLikeComponent() / 1_GeV, + targetP4.getSpaceLikeComponents().getComponents() / 1_GeV); + + // sqrtS per target nucleon + HEPEnergyType const sqrtS = (projectileP4 + targetP4).getNorm(); + + CORSIKA_LOGGER_DEBUG(logger_, "sqrtS={}GeV", sqrtS / 1_GeV); + + // accepts only photon-nucleon interactions + if (!isValid(projectileId, targetId, sqrtS)) { + CORSIKA_LOGGER_ERROR(logger_, "Invalid target/projectile/energy combination"); + throw std::runtime_error("SOPHIA: Invalid target/projectile/energy combination"); + } + + auto const nucleonId = (projectileId == Code::Photon ? targetId : projectileId); + auto const nucleonP4 = (projectileId == Code::Photon ? targetP4 : projectileP4); + auto const photonP4 = (projectileId == Code::Photon ? projectileP4 : targetP4); + COMBoost const boost(projectileP4, targetP4); + + int nucleonSophiaCode = convertToSophiaRaw(nucleonId); // either proton or neutron + // initialize resonance spectrum + initial_(nucleonSophiaCode); + double Enucleon = nucleonP4.getTimeLikeComponent() / 1_GeV; + double Ephoton = photonP4.getTimeLikeComponent() / 1_GeV; + double theta = 0.0; // set head on collision + int Imode; + CORSIKA_LOGGER_DEBUG(logger_, + "calling SOPHIA eventgen with L0={}, E0={}, eps={},theta={}", + nucleonSophiaCode, Enucleon, Ephoton, theta); + count_++; + // call sophia + eventgen_(nucleonSophiaCode, Enucleon, Ephoton, theta, Imode); + + if (sophia_listing_) { + int arg = 3; + print_event_(arg); + } + + auto const& originalCS = boost.getOriginalCS(); + // SOPHIA has photon along -z and nucleon along +z (GZK calc..) + COMBoost const boostInternal(nucleonP4, photonP4); + auto const& csPrime = boost.getRotatedCS(); + CoordinateSystemPtr csPrimePrime = + make_rotation(csPrime, QuantityVector<length_d>{1_m, 0_m, 0_m}, M_PI); + + SophiaStack ss; + + MomentumVector P_final(originalCS, {0.0_GeV, 0.0_GeV, 0.0_GeV}); + HEPEnergyType E_final = 0_GeV; + for (auto& psop : ss) { + // abort on particles that have decayed in SOPHIA. Should not happen! + if (psop.hasDecayed()) { // LCOV_EXCL_START + throw std::runtime_error("found particle that decayed in SOPHIA!"); + } // LCOV_EXCL_STOP + + auto momentumSophia = psop.getMomentum(csPrimePrime); + momentumSophia.rebase(csPrime); + auto const energySophia = psop.getEnergy(); + auto const P4com = boostInternal.toCoM(FourVector{energySophia, momentumSophia}); + auto const P4lab = boost.fromCoM(P4com); + SophiaCode const pidSophia = psop.getPID(); + Code const pid = convertFromSophia(pidSophia); + auto momentum = P4lab.getSpaceLikeComponents(); + momentum.rebase(originalCS); + HEPEnergyType const Ekin = + calculate_kinetic_energy(momentum.getNorm(), get_mass(pid)); + + CORSIKA_LOGGER_TRACE(logger_, "SOPHIA: pid={}, p={} GeV", pidSophia, + momentumSophia.getComponents() / 1_GeV); + + CORSIKA_LOGGER_TRACE(logger_, "CORSIKA: pid={}, p={} GeV", pid, + momentum.getComponents() / 1_GeV); + + auto pnew = + secondaries.addSecondary(std::make_tuple(pid, Ekin, momentum.normalized())); + + P_final += pnew.getMomentum(); + E_final += pnew.getEnergy(); + } + CORSIKA_LOGGER_TRACE(logger_, "Efinal={} GeV,Pfinal={} GeV", E_final / 1_GeV, + P_final.getComponents() / 1_GeV); + } + +} // namespace corsika::sophia \ No newline at end of file diff --git a/corsika/modules/Sophia.hpp b/corsika/modules/Sophia.hpp index 85522208b8973d6571d885af7cb5a0d1bbd9e694..48b4b6706defad2bbd166cab2438ec2b84941343 100644 --- a/corsika/modules/Sophia.hpp +++ b/corsika/modules/Sophia.hpp @@ -28,10 +28,10 @@ namespace corsika::sophia { * The sophia::InteractionModel is wrapped as an InteractionProcess here in order * to provide all the functions for ProcessSequence. */ - struct Interaction : public InteractionModel, public InteractionProcess<Interaction> { - template <typename TEnvironment> - Interaction(TEnvironment const& env) - : InteractionModel{env} {} - }; + // struct Interaction : public InteractionModel, public InteractionProcess<Interaction> { + // template <typename TEnvironment> + // Interaction(TEnvironment const& env) + // : InteractionModel{env} {} + // }; } // namespace corsika::sophia diff --git a/corsika/modules/sophia/InteractionModel.hpp b/corsika/modules/sophia/InteractionModel.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e2f6d7636216242ac0c61a46115bd9a2f6a65773 --- /dev/null +++ b/corsika/modules/sophia/InteractionModel.hpp @@ -0,0 +1,105 @@ +/* + * (c) Copyright 2022 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/ParticleProperties.hpp> +#include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/framework/random/RNGManager.hpp> +#include <corsika/framework/geometry/FourVector.hpp> + +#include <tuple> + +namespace corsika::sophia { + + /** + * @brief Provides the SOPHIA photon-nucleon interaction model. + * + * This is a TModel argument for InteractionProcess<TModel>. + */ + + class InteractionModel { + + public: + InteractionModel(); + ~InteractionModel(); + + /** + * @brief Set the Verbose flag. + * + * If flag is true, SOPHIA will printout additional secondary particle information + * lists, etc. + * + * @param flag to switch. + */ + void setVerbose(bool const flag); + + /** + * @brief evaluated validity of collision system. + * + * SOPHIA only accepts nucleons as targets, or protons aka Hydrogen or + * neutrons (p,n == nucleon). + */ + bool constexpr isValid(Code const projectileId, Code const targetId, + HEPEnergyType const sqrtSnn) const; + + /** + * Returns inelastic (production) cross section. + * + * This cross section must correspond to the process described in doInteraction. + * Allowed targets are: nuclei or single nucleons (p,n,hydrogen). + * + * @param projectile is the Code of the projectile + * @param target is the Code of the target + * @param projectileP4: four-momentum of projectile + * @param targetP4: four-momentum of target + * + * @return inelastic cross section + * elastic cross section + */ + CrossSectionType getCrossSection(Code const projectile, Code const target, + FourMomentum const& projectileP4, + FourMomentum const& targetP4) const { + return CrossSectionType::zero(); + } + /** + * In this function SOPHIA is called to produce one event. The + * event is copied (and boosted) into the frame of the incoming particles. + * + * @param view is the stack object for the secondaries + * @param projectile is the Code of the projectile + * @param target is the Code of the target + * @param projectileP4: four-momentum of projectile + * @param targetP4: four-momentum of target + */ + + template <typename TSecondaries> + void doInteraction(TSecondaries& view, Code const projectile, Code const target, + FourMomentum const& projectileP4, FourMomentum const& targetP4); + + private: + HEPEnergyType constexpr getMinEnergyCoM() const { return minEnergyCoM_; } + HEPEnergyType constexpr getMaxEnergyCoM() const { return maxEnergyCoM_; } + + // hard model limits + static HEPEnergyType constexpr minEnergyCoM_ = 1.079166345 * 1e9 * electronvolt; + static HEPEnergyType constexpr maxEnergyCoM_ = 1.e6 * 1e9 * electronvolt; + + default_prng_type& RNG_ = RNGManager<>::getInstance().getRandomStream("sophia"); + + // data members + int count_ = 0; + bool sophia_listing_; + + std::shared_ptr<spdlog::logger> logger_ = + get_logger("corsika_sophia_InteractionModel"); + }; + +} // namespace corsika::sophia + +#include <corsika/detail/modules/sophia/InteractionModel.inl> \ No newline at end of file diff --git a/modules/sophia/sophia.hpp b/modules/sophia/sophia.hpp index b2564f195cbffca8714bb6f99faf35d363bab952..fbf7e0e64424b8eaaa8f47c8a378e25499a62850 100644 --- a/modules/sophia/sophia.hpp +++ b/modules/sophia/sophia.hpp @@ -76,11 +76,14 @@ extern struct { double am2[49]; } so_mass1_; +// sophia initialization +void initial_(const int&); + // sophia main subroutine -void eventgen_(const int&, const double&, const double&, const double&, const int&); +void eventgen_(const int&, const double&, const double&, const double&, int&); // print event -void print_event_(int&); +void print_event_(const int&); // decay routine (LA,P0,ND,LL,P) // void decpar_(const int&, const double*, int&, int*, double*); diff --git a/tests/modules/testSophia.cpp b/tests/modules/testSophia.cpp index 16170f1dabc86b08c9f8225433b15af9bd8277c0..fc843e653a122e6da4f4824349c2d8bf2859ad40 100644 --- a/tests/modules/testSophia.cpp +++ b/tests/modules/testSophia.cpp @@ -6,7 +6,7 @@ * the license. */ -//#include <corsika/modules/Sibyll.hpp> +#include <corsika/modules/Sophia.hpp> #include <corsika/modules/sophia/ParticleConversion.hpp> #include <corsika/framework/core/ParticleProperties.hpp> @@ -42,12 +42,13 @@ TEST_CASE("Sophia", "modules") { SECTION("Sophia -> Corsika") { CHECK(Code::Electron == corsika::sophia::convertFromSophia(corsika::sophia::SophiaCode::Electron)); + CHECK_THROWS(convertFromSophia(corsika::sophia::SophiaCode::Unknown)); } SECTION("Corsika -> Sophia") { CHECK(corsika::sophia::convertToSophia(Electron::code) == corsika::sophia::SophiaCode::Electron); - CHECK(corsika::sophia::convertToSophiaRaw(Proton::code) == 13); + CHECK(corsika::sophia::convertToSophiaRaw(Proton::code) == 13); } SECTION("canInteractInSophia") { @@ -56,7 +57,7 @@ TEST_CASE("Sophia", "modules") { CHECK_FALSE(corsika::sophia::canInteract(Code::XiCPlus)); CHECK_FALSE(corsika::sophia::canInteract(Code::Electron)); - + CHECK_FALSE(corsika::sophia::canInteract(Code::Iron)); CHECK_FALSE(corsika::sophia::canInteract(Code::Helium)); } @@ -74,3 +75,71 @@ TEST_CASE("Sophia", "modules") { CHECK_THROWS(corsika::sophia::getSophiaMass(Code::H0)); } } + +#include <corsika/framework/geometry/Point.hpp> +#include <corsika/framework/geometry/RootCoordinateSystem.hpp> +#include <corsika/framework/geometry/Vector.hpp> + +#include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/framework/core/ParticleProperties.hpp> + +#include <SetupTestEnvironment.hpp> +#include <SetupTestStack.hpp> + +#include <corsika/media/Environment.hpp> +#include <corsika/media/HomogeneousMedium.hpp> +#include <corsika/media/NuclearComposition.hpp> +#include <corsika/media/UniformMagneticField.hpp> + +template <typename TStackView> +auto sumMomentum(TStackView const& view, CoordinateSystemPtr const& vCS) { + Vector<hepenergy_d> sum{vCS, 0_eV, 0_eV, 0_eV}; + for (auto const& p : view) { sum += p.getMomentum(); } + return sum; +} + +TEST_CASE("SophiaInterface", "modules") { + + logging::set_level(logging::level::debug); + + // the environment and stack should eventually disappear from here + auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Oxygen); + auto const& cs = *csPtr; + { [[maybe_unused]] auto const& env_dummy = env; } + + auto [stack, viewPtr] = setup::testing::setup_stack( + Code::Photon, 10_GeV, (DummyEnvironment::BaseNodeType* const)nodePtr, cs); + test::StackView& view = *viewPtr; + + RNGManager<>::getInstance().registerRandomStream("sophia"); + + SECTION("InteractionInterface - valid targets/projectiles") { + + corsika::sophia::InteractionModel model; + + CHECK_FALSE(model.isValid(Code::Proton, Code::Electron, 100_GeV)); + CHECK(model.isValid(Code::Photon, Code::Hydrogen, 3_GeV)); + + FourMomentum const aP4(100_GeV, {cs, 99_GeV, 0_GeV, 0_GeV}); + FourMomentum const bP4(1_GeV, {cs, 0_GeV, 0_GeV, 0_GeV}); + CHECK(0_mb == model.getCrossSection(Code::Photon, Code::Proton, aP4, bP4)); + } + + SECTION("InteractionInterface - interaction") { + const HEPEnergyType P0 = 1.2_GeV; + MomentumVector const plab = MomentumVector(cs, {P0, 0_eV, 0_eV}); + // also print particles after SOPHIA was called + corsika::sophia::InteractionModel model; + model.setVerbose(true); + HEPEnergyType const Elab = P0; + FourMomentum const projectileP4(Elab, plab); + FourMomentum const nucleonP4(Proton::mass, MomentumVector(cs, {0_eV, 0_eV, 0_eV})); + view.clear(); + model.doInteraction(view, Code::Photon, Code::Proton, projectileP4, nucleonP4); + + auto const pSum = sumMomentum(view, cs); + CHECK(pSum.getComponents(cs).getX() / P0 == Approx(1).margin(0.05)); + CHECK(pSum.getComponents(cs).getY() / 1_GeV == Approx(0).margin(1e-3)); + CHECK(pSum.getComponents(cs).getZ() / 1_GeV == Approx(0).margin(1e-3)); + } +} \ No newline at end of file