diff --git a/corsika/detail/modules/ParticleCut.inl b/corsika/detail/modules/ParticleCut.inl index b5853df3bd27b00cf8af56a37b82dca0d82613e9..789cf5b5972a1a00ccbb6b02117afad12bb05c33 100644 --- a/corsika/detail/modules/ParticleCut.inl +++ b/corsika/detail/modules/ParticleCut.inl @@ -13,11 +13,13 @@ namespace corsika { template <typename TOutput> - inline ParticleCut<TOutput>::ParticleCut(TOutput& output, HEPEnergyType const eEleCut, + template <typename... TArgs> + inline ParticleCut<TOutput>::ParticleCut(HEPEnergyType const eEleCut, HEPEnergyType const ePhoCut, HEPEnergyType const eHadCut, - HEPEnergyType const eMuCut, bool const inv) - : output_(output) + HEPEnergyType const eMuCut, bool const inv, + TArgs&&... args) + : TOutput(std::forward<TArgs>(args)...) , doCutInv_(inv) , energy_cut_(0_GeV) , energy_timecut_(0_GeV) @@ -45,10 +47,8 @@ namespace corsika { template <typename TOutput> inline ParticleCut<TOutput>::ParticleCut( - TOutput& output, std::unordered_map<Code const, HEPEnergyType const> const& eCuts, - bool const inv) - : output_(output) - , doCutInv_(inv) + std::unordered_map<Code const, HEPEnergyType const> const& eCuts, bool const inv) + : doCutInv_(inv) , energy_cut_(0_GeV) , energy_timecut_(0_GeV) , energy_invcut_(0_GeV) @@ -127,8 +127,8 @@ namespace corsika { auto particle = vS.begin(); while (particle != vS.end()) { if (checkCutParticle(particle)) { - output_.write(particle.getPosition(), particle.getPID(), - particle.getKineticEnergy()); + this->write(particle.getPosition(), particle.getPID(), + particle.getKineticEnergy()); particle.erase(); } ++particle; // next entry in SecondaryView @@ -141,8 +141,7 @@ namespace corsika { inline ProcessReturn ParticleCut<TOutput>::doContinuous(TParticle& particle, TTrajectory const&, bool const) { if (checkCutParticle(particle)) { - output_.write(particle.getPosition(), particle.getPID(), - particle.getKineticEnergy()); + this->write(particle.getPosition(), particle.getPID(), particle.getKineticEnergy()); CORSIKA_LOG_TRACE("removing during continuous"); // signal to upstream code that this particle was deleted return ProcessReturn::ParticleAbsorbed; @@ -181,4 +180,18 @@ namespace corsika { energy_timecut_ = 0_GeV; } + template <typename TOutput> + inline YAML::Node ParticleCut<TOutput>::getConfig() const { + + YAML::Node node; + node["type"] = "ParticleCut"; + node["units"]["energy"] = "GeV"; + node["energy_invcut"] = energy_invcut_ / 1_GeV; + node["inv_count"] = inv_count_; + node["energy_cut"] = energy_cut_ / 1_GeV; + node["energy_timecut_"] = energy_timecut_ / 1_GeV; + + return node; + } + } // namespace corsika diff --git a/corsika/detail/modules/writers/ParticleCutWriterParquet.inl b/corsika/detail/modules/writers/ParticleCutWriterParquet.inl new file mode 100644 index 0000000000000000000000000000000000000000..1405e9416a8ff916b6775b5b18ee1b3e4441ffc5 --- /dev/null +++ b/corsika/detail/modules/writers/ParticleCutWriterParquet.inl @@ -0,0 +1,64 @@ +/* + * (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 + +namespace corsika { + + inline ParticleCutWriterParquet::ParticleCutWriterParquet() + : output_() {} + + inline void ParticleCutWriterParquet::startOfLibrary( + boost::filesystem::path const& directory) { + + // setup the streamer + output_.initStreamer((directory / "energyloss.parquet").string()); + + // build the schema + output_.addField("pdg", parquet::Repetition::REQUIRED, parquet::Type::INT32, + parquet::ConvertedType::INT_32); + output_.addField("energy", parquet::Repetition::REQUIRED, parquet::Type::FLOAT, + parquet::ConvertedType::NONE); + output_.addField("x", parquet::Repetition::REQUIRED, parquet::Type::FLOAT, + parquet::ConvertedType::NONE); + output_.addField("y", parquet::Repetition::REQUIRED, parquet::Type::FLOAT, + parquet::ConvertedType::NONE); + output_.addField("z", parquet::Repetition::REQUIRED, parquet::Type::FLOAT, + parquet::ConvertedType::NONE); + + // and build the streamer + output_.buildStreamer(); + } + + inline void ParticleCutWriterParquet::startOfShower(unsigned int const showerId) { + showerId_ = showerId; + } + + inline void ParticleCutWriterParquet::endOfShower(unsigned int const) {} + + inline void ParticleCutWriterParquet::endOfLibrary() { output_.closeStreamer(); } + + inline void ParticleCutWriterParquet::write(Point const& point, Code const pid, + HEPEnergyType energy) { + + auto location{point.getCoordinates()}; + + // write the next row - we must write `shower_` first. + // clang-format off + *(output_.getWriter()) + << showerId_ + << static_cast<int>(get_PDG(pid)) + << static_cast<float>(energy / 1_GeV) + << static_cast<float>(location[0] / 1_m) + << static_cast<float>(location[1] / 1_m) + << static_cast<float>(location[2] / 1_m) + << parquet::EndRow; + // clang-format on + } + +} // namespace corsika diff --git a/corsika/modules/ParticleCut.hpp b/corsika/modules/ParticleCut.hpp index 3db5d0d30ec7819d3800de359e70b892d8c8c7fc..7ce7139ed4ee6c7cd013a299a5b98b992b235647 100644 --- a/corsika/modules/ParticleCut.hpp +++ b/corsika/modules/ParticleCut.hpp @@ -15,6 +15,8 @@ #include <corsika/framework/process/SecondariesProcess.hpp> #include <corsika/framework/process/ContinuousProcess.hpp> +#include <corsika/modules/writers/WriterOff.hpp> + namespace corsika { /** simple ParticleCut process. Goes through the secondaries of an interaction and @@ -24,9 +26,10 @@ namespace corsika { for each particle. Special constructors for cuts by the following groups are implemented: (electrons,positrons), photons, hadrons and muons. **/ - template <typename TOutput = EnergyLossWriterOff> + template <typename TOutput = WriterOff> class ParticleCut : public SecondariesProcess<ParticleCut<TOutput>>, - public ContinuousProcess<ParticleCut<TOutput>> { + public ContinuousProcess<ParticleCut<TOutput>>, + public TOutput { public: /** @@ -34,30 +37,17 @@ namespace corsika { * hadrons (including nuclei with energy per nucleon) and muons * invisible particles (neutrinos) can be cut or not */ - ParticleCut(TOutput& output, HEPEnergyType const eEleCut, HEPEnergyType const ePhoCut, - HEPEnergyType const eHadCut, HEPEnergyType const eMuCut, bool const inv); - - /** - * Same, but with no output. - */ + template <typename... TArgs> ParticleCut(HEPEnergyType const eEleCut, HEPEnergyType const ePhoCut, - HEPEnergyType const eHadCut, HEPEnergyType const eMuCut, bool const inv) - : ParticleCut(*(new EnergyLossWriterOff()), eEleCut, ePhoCut, eHadCut, eMuCut, - inv) {} + HEPEnergyType const eHadCut, HEPEnergyType const eMuCut, bool const inv, + TArgs&&... args); /** * Threshold for specific particles redefined. EM and invisible particles can be set * to be discarded altogether. */ - ParticleCut(TOutput& output, - std::unordered_map<Code const, HEPEnergyType const> const& eCuts, - bool const inv); - /** - * Same, but with no output. - */ ParticleCut(std::unordered_map<Code const, HEPEnergyType const> const& eCuts, - bool const inv) - : ParticleCut(*(new EnergyLossWriterOff()), eCuts, inv) {} + bool const inv); template <typename TStackView> void doSecondaries(TStackView&); @@ -97,6 +87,9 @@ namespace corsika { //! returns number of invisible particles unsigned int getNumberInvParticles() const { return inv_count_; } + // get configuration of this node + YAML::Node getConfig() const override; + private: template <typename TParticle> bool checkCutParticle(TParticle const& p); @@ -108,7 +101,6 @@ namespace corsika { bool isInvisible(Code const&) const; private: - TOutput& output_; bool doCutInv_; HEPEnergyType energy_cut_ = 0 * electronvolt; HEPEnergyType energy_timecut_ = 0 * electronvolt; diff --git a/corsika/modules/writers/ParticleCutWriterParquet.hpp b/corsika/modules/writers/ParticleCutWriterParquet.hpp new file mode 100644 index 0000000000000000000000000000000000000000..466bf55719e044a2749b26a39a8388e0d43cfbf3 --- /dev/null +++ b/corsika/modules/writers/ParticleCutWriterParquet.hpp @@ -0,0 +1,61 @@ +/* + * (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 <corsika/output/BaseOutput.hpp> +#include <corsika/output/ParquetStreamer.hpp> +#include <corsika/framework/core/ParticleProperties.hpp> +#include <corsika/framework/core/PhysicalUnits.hpp> + +namespace corsika { + + class ParticleCutWriterParquet : public BaseOutput { + + public: + /** + * Construct a new writer. + */ + ParticleCutWriterParquet(); + + /** + * Called at the start of each library. + */ + void startOfLibrary(boost::filesystem::path const& directory) final override; + + /** + * Called at the start of each shower. + */ + void startOfShower(unsigned int const showerId) final override; + + /** + * Called at the end of each shower. + */ + void endOfShower(unsigned int const showerId) final override; + + /** + * Called at the end of each library. + * + * This must also increment the run number since we override + * the default behaviour of BaseOutput. + */ + void endOfLibrary() final override; + + /** + * Write a particle cut. + */ + void write(Point const& point, Code const PID, HEPEnergyType const energy); + + private: + ParquetStreamer output_; ///< The parquet streamer for this process. + unsigned int showerId_{0}; + }; // namespace corsika + +} // namespace corsika + +#include <corsika/detail/modules/writers/ParticleCutWriterParquet.inl> diff --git a/examples/vertical_EAS.cpp b/examples/vertical_EAS.cpp index 8f9f984dc5da4d35b761cd92187e885149b22a02..2023d527bd48ffda3af586c6d1cc3721b3b82ced 100644 --- a/examples/vertical_EAS.cpp +++ b/examples/vertical_EAS.cpp @@ -51,6 +51,7 @@ #include <corsika/modules/StackInspector.hpp> #include <corsika/modules/TrackWriter.hpp> #include <corsika/modules/ParticleCut.hpp> +#include <corsika/modules/writers/ParticleCutWriterParquet.hpp> #include <corsika/modules/Pythia8.hpp> #include <corsika/modules/Sibyll.hpp> #include <corsika/modules/UrQMD.hpp> @@ -245,7 +246,8 @@ int main(int argc, char** argv) { decaySibyll.printDecayConfig(); - ParticleCut cut{60_GeV, 60_GeV, 60_GeV, 60_GeV, true}; + ParticleCut<ParticleCutWriterParquet> cut{60_GeV, 60_GeV, 60_GeV, 60_GeV, true}; + output.add("cuts", cut); corsika::urqmd::UrQMD urqmd; InteractionCounter urqmdCounted{urqmd}; diff --git a/python/corsika/io/outputs/__init__.py b/python/corsika/io/outputs/__init__.py index b86fb6e70e83d06d4570ba65dc2c9a627177d5e7..d39d9a8b4bd68465ab1d799f10c15bb20032cbc9 100644 --- a/python/corsika/io/outputs/__init__.py +++ b/python/corsika/io/outputs/__init__.py @@ -11,6 +11,14 @@ from .observation_plane import ObservationPlane from .track_writer import TrackWriter from .longitudinal_profile import LongitudinalProfile from .bethe_bloch import BetheBlochPDG +from .particle_cut import ParticleCut from .output import Output -__all__ = ["Output", "ObservationPlane", "TrackWriter", "LongitudinalProfile"] +__all__ = [ + "Output", + "ObservationPlane", + "TrackWriter", + "LongitudinalProfile", + "BetheBlochPDG", + "ParticleCut", +] diff --git a/python/corsika/io/outputs/particle_cut.py b/python/corsika/io/outputs/particle_cut.py new file mode 100644 index 0000000000000000000000000000000000000000..a55b5fe718cf788fdba051dfa891e77b2d75ede7 --- /dev/null +++ b/python/corsika/io/outputs/particle_cut.py @@ -0,0 +1,87 @@ +""" + Read data written by ParticleCut. + + (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. +""" +import logging +import os.path as op +from typing import Any + +import pyarrow.parquet as pq + +from .output import Output + + +class ParticleCut(Output): + """ + Read particle data from an ParticleCut. + """ + + def __init__(self, path: str): + """ + Load the particle data into a parquet table. + + Parameters + ---------- + path: str + The path to the directory containing this output. + """ + super().__init__(path) + + # try and load our data + try: + self.__data = pq.read_table(op.join(path, "energyloss.parquet")) + except Exception as e: + logging.getLogger("corsika").warn( + f"An error occured loading a ParticleCut: {e}" + ) + + def is_good(self) -> bool: + """ + Returns true if this output has been read successfully + and has the correct files/state/etc. + + Returns + ------- + bool: + True if this is a good output. + """ + return self.__data is not None + + def astype(self, dtype: str = "pandas", **kwargs: Any) -> Any: + """ + Load the particle data from this particle cut instance. + + All additional keyword arguments are passed to `parquet.read_table` + + Parameters + ---------- + dtype: str + The data format to return the data in (i.e. numpy, pandas, etc.) + + Returns + ------- + Any: + The return type of this method is determined by `dtype`. + """ + if dtype == "arrow": + return self.__data + elif dtype == "pandas": + return self.__data.to_pandas() + else: + raise ValueError( + ( + f"Unknown format '{dtype}' for ParticleCut. " + "We currently only support ['arrow', 'pandas']." + ) + ) + + def __repr__(self) -> str: + """ + Return a string representation of this class. + """ + return f"ParticleCut('{self.config['name']}')"