diff --git a/README.md b/README.md index ff506af0ed6de2f319780d7cce904dfbadc36614..105f090a6b4477d5544a6b6f36ca8cec488c26f5 100644 --- a/README.md +++ b/README.md @@ -142,25 +142,26 @@ To run the Unit Tests, just type `ctest` in your build area. ## Running applications and examples -### Building the applications +### Standard applications + +Applications for standard use-cases are located in the `applications` directory. +These are example scripts that can be used directly or slightly modified for your use case. +See [applications/README.md] for more. +The applications are compiled automatically after running `make` and will appear your `corsika-build/bin` directory. +After running `make install` the binaries will also be copied into your `corsika-install/bin` directory as well. -From your top corsika build directory, (the one that includes `corsika-build` and `corsika-install`) type -```shell -cmake -Dcorsika_DIR=$PWD/corsika-build -S ./corsika/applications -B ./corsika-build-applications -cd corsika-build-applications -make -j4 #The number should match the number of available cores on your machine -``` -Run a simple air shower using +For example, from inside your `corsika-install/bin` directory, run ```shell -corsika-build-applications/bin/air_shower --pdg 2212 -E 1e5 -f my_shower +c8_air_shower --pdg 2212 -E 1e5 -f my_shower ``` This will run a vertical 100 TeV proton shower and will create and put the output into `./my_shower`. ### Building the examples -From your top corsika build directory, (the one that includes `corsika-build` and `corsika-install`) type +Unlike the applications, the examples must be compiled as a second step. +From your top corsika directory, (the one that includes `corsika-build` and `corsika-install`) run ```shell cmake -Dcorsika_DIR=$PWD/corsika-build -S ./corsika/examples -B ./corsika-build-examples cd corsika-build-examples diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 5f75fea2d6c8d176e3aaa0f2e238f13ea0119354..a871f47dc0fa87d890708e8269e924f6d378787e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -26,6 +26,10 @@ else() message("FLUKA not found, will not make 'mars' example") endif() +add_executable (hybrid_MC cascade_examples/hybrid_MC.cpp) +target_link_libraries (hybrid_MC CORSIKA8::CORSIKA8) +CORSIKA_REGISTER_EXAMPLE (hybrid_MC RUN_OPTIONS 4 2 10000.) + add_executable (radio_em_shower cascade_examples/radio_em_shower.cpp) target_link_libraries (radio_em_shower CORSIKA8::CORSIKA8) CORSIKA_REGISTER_EXAMPLE (radio_em_shower RUN_OPTIONS 10 1121673489) diff --git a/examples/cascade_examples/hybrid_MC.cpp b/examples/cascade_examples/hybrid_MC.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5ed3e96137ded34c0975b39ad3e0f473bf0abc70 --- /dev/null +++ b/examples/cascade_examples/hybrid_MC.cpp @@ -0,0 +1,311 @@ +/* + * (c) Copyright 2018 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/Cascade.hpp> +#include <corsika/framework/core/EnergyMomentumOperations.hpp> +#include <corsika/framework/core/Logging.hpp> +#include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/framework/core/Step.hpp> +#include <corsika/framework/geometry/PhysicalGeometry.hpp> +#include <corsika/framework/geometry/Plane.hpp> +#include <corsika/framework/geometry/Sphere.hpp> +#include <corsika/framework/process/InteractionCounter.hpp> +#include <corsika/framework/process/ProcessSequence.hpp> +#include <corsika/framework/process/SwitchProcessSequence.hpp> +#include <corsika/framework/random/RNGManager.hpp> +#include <corsika/framework/utility/SaveBoostHistogram.hpp> +// #include <corsika/framework/utility/CorsikaFenv.hpp> + +#include <corsika/modules/writers/EnergyLossWriter.hpp> +#include <corsika/modules/writers/LongitudinalWriter.hpp> +#include <corsika/modules/writers/PrimaryWriter.hpp> +#include <corsika/modules/writers/SubWriter.hpp> +#include <corsika/output/OutputManager.hpp> + +#include <corsika/media/CORSIKA7Atmospheres.hpp> +#include <corsika/media/Environment.hpp> +#include <corsika/media/LayeredSphericalAtmosphereBuilder.hpp> +#include <corsika/media/MediumPropertyModel.hpp> +#include <corsika/media/ShowerAxis.hpp> +#include <corsika/media/UniformMagneticField.hpp> + +#include <corsika/modules/BetheBlochPDG.hpp> +#include <corsika/modules/LongitudinalProfile.hpp> +#include <corsika/modules/ObservationPlane.hpp> +#include <corsika/modules/ParticleCut.hpp> +#include <corsika/modules/PROPOSAL.hpp> +#include <corsika/modules/Pythia8.hpp> +#include <corsika/modules/Sibyll.hpp> +#include <corsika/modules/TrackWriter.hpp> +#include <corsika/modules/UrQMD.hpp> +#include <corsika/modules/CONEX.hpp> + +#include <corsika/setup/SetupStack.hpp> +#include <corsika/setup/SetupTrajectory.hpp> + +#include <iomanip> +#include <iostream> +#include <limits> +#include <string> + +using namespace corsika; +using namespace std; + +// +// An example of running an EAS where the hadronic cascade is +// handled by sibyll+URQMD and the EM cascade is treated with +// CONEX + Bethe Bloch (as opposed to PROPOSAL). +// + +/** + * Random number stream initialization + * + * @param seed + */ +void registerRandomStreams(uint64_t seed) { + RNGManager<>::getInstance().registerRandomStream("cascade"); + RNGManager<>::getInstance().registerRandomStream("conex"); + RNGManager<>::getInstance().registerRandomStream("epos"); + RNGManager<>::getInstance().registerRandomStream("proposal"); + RNGManager<>::getInstance().registerRandomStream("pythia"); + RNGManager<>::getInstance().registerRandomStream("qgsjet"); + RNGManager<>::getInstance().registerRandomStream("sibyll"); + RNGManager<>::getInstance().registerRandomStream("urqmd"); + if (seed == 0) { + std::random_device rd; + seed = rd(); + CORSIKA_LOG_INFO("random seed (auto) {} ", seed); + } else { + CORSIKA_LOG_INFO("random seed {} ", seed); + } + RNGManager<>::getInstance().setSeed(seed); +} + +/** + * New (for demonstration) ContinuousProcess which will check if a particles has traversed + * below the observation level. + */ +class TrackCheck : public ContinuousProcess<TrackCheck> { + +public: + /** + * Construct a new Track Check object. + * + * @param plane -- the actual observation level + */ + TrackCheck(Plane const& plane) + : plane_(plane) {} + + /** + * The doContinous method to check a particular particle. + * + * @tparam TParticle + * @tparam TTrack + * @param particle + * @return ProcessReturn + */ + template <typename TParticle> + ProcessReturn doContinuous(Step<TParticle> const& step, bool const) { + auto const delta = step.getParticlePre().getPosition() - plane_.getCenter(); + auto const n = plane_.getNormal(); + auto const proj = n.dot(delta); + if (proj < -1_m) { + CORSIKA_LOG_INFO("particle {} failes: proj={}, delta={}, p={}", + step.getParticlePre().asString(), proj, delta, + step.getPositionPost()); + throw std::runtime_error("particle below obs level"); + } + return ProcessReturn::Ok; + } + + /** + * No limit on tracking step length imposed here, of course. + * + * @tparam TParticle + * @tparam TTrack + * @return LengthType + */ + template <typename TParticle, typename TTrack> + LengthType getMaxStepLength(TParticle const&, TTrack const&) const { + return std::numeric_limits<double>::infinity() * 1_m; + } + +private: + Plane plane_; +}; + +/** + * Selection of environment interface implementation: + */ +using EnvironmentInterface = IMediumPropertyModel<IMagneticFieldModel<IMediumModel>>; +using EnvType = Environment<EnvironmentInterface>; +template <typename T> +using MyExtraEnv = MediumPropertyModel<UniformMagneticField<T>>; +using StackType = setup::Stack<EnvType>; +using TrackingType = setup::Tracking; + +int main(int argc, char** argv) { + + logging::set_level(logging::level::info); + + CORSIKA_LOG_INFO("hybrid_MC"); + + if (argc < 4) { + CORSIKA_LOG_ERROR( + "\n" + "usage: hybrid_MC <A> <Z> <energy/GeV> [seed] \n" + " if no seed is given, a random seed is chosen"); + return 1; + } + + uint64_t seed = 0; + if (argc > 4) seed = std::stol(std::string(argv[4])); + // initialize random number sequence(s) + registerRandomStreams(seed); + + // setup environment, geometry + EnvType env; + CoordinateSystemPtr const& rootCS = env.getCoordinateSystem(); + Point const center{rootCS, 0_m, 0_m, 0_m}; + + // build a Linsley US Standard atmosphere into `env` + MagneticFieldVector bField{rootCS, 50_uT, 0_T, 0_T}; + create_5layer_atmosphere<EnvironmentInterface, MyExtraEnv>( + env, AtmosphereId::LinsleyUSStd, center, Medium::AirDry1Atm, bField); + + unsigned short const A = std::stoi(std::string(argv[1])); + unsigned short const Z = std::stoi(std::string(argv[2])); + Code const beamCode = get_nucleus_code(A, Z); + auto const mass = get_mass(beamCode); + HEPEnergyType const E0 = 1_GeV * std::stof(std::string(argv[3])); + double theta = 0.; + auto const thetaRad = theta / 180. * M_PI; + + HEPMomentumType P0 = calculate_momentum(E0, mass); + auto momentumComponents = [](double thetaRad, HEPMomentumType ptot) { + return std::make_tuple(ptot * sin(thetaRad), 0_eV, -ptot * cos(thetaRad)); + }; + + auto const [px, py, pz] = momentumComponents(thetaRad, P0); + auto plab = MomentumVector(rootCS, {px, py, pz}); + + auto const observationHeight = 0_km + constants::EarthRadius::Mean; + auto const injectionHeight = 112.75_km + constants::EarthRadius::Mean; + auto const t = -observationHeight * cos(thetaRad) + + sqrt(-static_pow<2>(sin(thetaRad) * observationHeight) + + static_pow<2>(injectionHeight)); + Point const showerCore{rootCS, 0_m, 0_m, observationHeight}; + Point const injectionPos = + showerCore + + Vector<dimensionless_d>{rootCS, {-sin(thetaRad), 0, cos(thetaRad)}} * t; + + ShowerAxis const showerAxis{injectionPos, (showerCore - injectionPos) * 1.02, env, + false, 1000}; + + CORSIKA_LOG_INFO("Primary particle: {}", beamCode); + CORSIKA_LOG_INFO("Zenith angle: {} (rad)", theta); + CORSIKA_LOG_INFO("Momentum: {} (GeV)", plab.getComponents() / 1_GeV); + CORSIKA_LOG_INFO("Propagation dir: {}", plab.getNorm()); + CORSIKA_LOG_INFO("Injection point: {}", injectionPos.getCoordinates()); + CORSIKA_LOG_INFO("shower axis length: {} ", + (showerCore - injectionPos).getNorm() * 1.02); + + // SETUP WRITERS + + OutputManager output("hybrid_MC_outputs"); + + // register energy losses as output + EnergyLossWriter dEdX{showerAxis, 10_g / square(1_cm), 200}; + output.add("energyloss", dEdX); + + // create a track writer and register it with the output manager + TrackWriter<TrackWriterParquet> tracks; + output.add("tracks", tracks); + + ParticleCut<SubWriter<decltype(dEdX)>> cut(3_GeV, false, dEdX); + BetheBlochPDG<SubWriter<decltype(dEdX)>> eLoss(dEdX); + + LongitudinalWriter profile{showerAxis, 10_g / square(1_cm)}; + output.add("profile", profile); + LongitudinalProfile<SubWriter<decltype(profile)>> longprof{profile}; + + Plane const obsPlane(showerCore, DirectionVector(rootCS, {0., 0., 1.})); + ObservationPlane<TrackingType> observationLevel( + obsPlane, DirectionVector(rootCS, {1., 0., 0.})); + output.add("particles", observationLevel); + + PrimaryWriter<TrackingType, ParticleWriterParquet> primaryWriter(observationLevel); + output.add("primary", primaryWriter); + + // SETUP PROCESSES, DECAYS, INTERACTIONS + + corsika::sibyll::Interaction sibyll{env}; + InteractionCounter sibyllCounted{sibyll}; + + corsika::pythia8::Decay decayPythia; + + CONEXhybrid // SubWriter<decltype(dEdX>, SubWriter<decltype(profile)>> + conex_model(center, showerAxis, t, injectionHeight, E0, get_PDG(Code::Proton), dEdX, + profile); + + corsika::urqmd::UrQMD urqmd_model; + InteractionCounter urqmdCounted{urqmd_model}; + + TrackCheck trackCheck(obsPlane); + + // assemble all processes into an ordered process list + struct EnergySwitch { + HEPEnergyType cutE_; + EnergySwitch(HEPEnergyType cutE) + : cutE_(cutE) {} + bool operator()(const StackType::particle_type& p) const { + return (p.getEnergy() < cutE_); + } + }; + auto hadronSequence = make_select(EnergySwitch(55_GeV), urqmdCounted, sibyllCounted); + auto sequence = make_sequence(hadronSequence, decayPythia, eLoss, cut, conex_model, + longprof, observationLevel, trackCheck); + + + StackType stack; + stack.clear(); + + // define air shower object, run simulation + TrackingType tracking; + Cascade EAS(env, tracking, sequence, output, stack); + + output.startOfLibrary(); + + auto const primaryProperties = std::make_tuple( + Code::Proton, calculate_kinetic_energy(plab.getNorm(), get_mass(beamCode)), + plab.normalized(), injectionPos, 0_ns); + + stack.addParticle(primaryProperties); + primaryWriter.recordPrimary(primaryProperties); + + // to fix the point of first interaction, uncomment the following two lines: + // EAS.SetNodes(); + // EAS.forceInteraction(); + + EAS.run(); + + const HEPEnergyType Efinal = dEdX.getEnergyLost() + observationLevel.getEnergyGround(); + CORSIKA_LOG_INFO( + "total cut energy (GeV): {}, " + "relative difference (%): {}", + Efinal / 1_GeV, (Efinal / E0 - 1) * 100); + + auto const hists = sibyllCounted.getHistogram() + urqmdCounted.getHistogram(); + + save_hist(hists.labHist(), "inthist_lab_hybrid.npz", true); + save_hist(hists.CMSHist(), "inthist_cms_hybrid.npz", true); + + output.endOfLibrary(); + + CORSIKA_LOG_INFO("done"); +}