diff --git a/corsika/detail/modules/writers/InteractionWriter.inl b/corsika/detail/modules/writers/InteractionWriter.inl index 87e0dd56beca5deb49b71de1f1265b44364a795f..e4d23bd50f587f25454c73f574175a988f3d07e5 100644 --- a/corsika/detail/modules/writers/InteractionWriter.inl +++ b/corsika/detail/modules/writers/InteractionWriter.inl @@ -18,8 +18,7 @@ namespace corsika { : obsPlane_(obsPlane) , showerAxis_(axis) , interactionCounter_(0) - , showerId_(0) - , nSecondaries_(0) {} + , showerId_(0) {} template <typename TTracking, typename TOutput> template <typename TStackView> @@ -32,47 +31,70 @@ namespace corsika { auto primary = vS.getProjectile(); auto dX = showerAxis_.getProjectedX(primary.getPosition()); CORSIKA_LOG_INFO("First interaction at dX {}", dX); - CORSIKA_LOG_INFO("Primary: {}, E {}", primary.getPID(), primary.getKineticEnergy()); - - // Get the displacement of the primary particle w.r.t. observation plane - Vector const dr_prim = primary.getPosition() - obsPlane_.getPlane().getCenter(); - auto const p_prim = primary.getMomentum(); - - *(output_.getWriter()) - << showerId_ << true << static_cast<int>(get_PDG(primary.getPID())) - << static_cast<float>(dr_prim.dot(obsPlane_.getXAxis()) / 1_m) - << static_cast<float>(dr_prim.dot(obsPlane_.getYAxis()) / 1_m) - << static_cast<float>(dr_prim.dot(obsPlane_.getPlane().getNormal()) / 1_m) - << static_cast<float>(p_prim.dot(obsPlane_.getXAxis()) / 1_GeV) - << static_cast<float>(p_prim.dot(obsPlane_.getYAxis()) / 1_GeV) - << static_cast<float>(p_prim.dot(obsPlane_.getPlane().getNormal()) / 1_GeV) - << static_cast<double>(primary.getTime() / 1_ns) - << static_cast<float>(dX / (1_g / 1_cm / 1_cm)) << parquet::EndRow; + CORSIKA_LOG_INFO("Primary: {}, E_kin {}", primary.getPID(), + primary.getKineticEnergy()); + + // get the location of the primary w.r.t. observation plane + Vector const displacement = primary.getPosition() - obsPlane_.getPlane().getCenter(); + + auto const x = displacement.dot(obsPlane_.getXAxis()); + auto const y = displacement.dot(obsPlane_.getYAxis()); + auto const z = displacement.dot(obsPlane_.getPlane().getNormal()); + auto const nx = primary.getDirection().dot(obsPlane_.getXAxis()); + auto const ny = primary.getDirection().dot(obsPlane_.getYAxis()); + auto const nz = primary.getDirection().dot(obsPlane_.getPlane().getNormal()); + + auto const px = primary.getMomentum().dot(obsPlane_.getXAxis()); + auto const py = primary.getMomentum().dot(obsPlane_.getYAxis()); + auto const pz = primary.getMomentum().dot(obsPlane_.getPlane().getNormal()); + + summary_["shower_" + std::to_string(showerId_)]["pdg"] = + static_cast<int>(get_PDG(primary.getPID())); + summary_["shower_" + std::to_string(showerId_)]["name"] = + static_cast<std::string>(get_name(primary.getPID())); + summary_["shower_" + std::to_string(showerId_)]["total_energy"] = + (primary.getKineticEnergy() + get_mass(primary.getPID())) / 1_GeV; + summary_["shower_" + std::to_string(showerId_)]["kinetic_energy"] = + primary.getKineticEnergy() / 1_GeV; + summary_["shower_" + std::to_string(showerId_)]["x"] = x / 1_m; + summary_["shower_" + std::to_string(showerId_)]["y"] = y / 1_m; + summary_["shower_" + std::to_string(showerId_)]["z"] = z / 1_m; + summary_["shower_" + std::to_string(showerId_)]["nx"] = static_cast<double>(nx); + summary_["shower_" + std::to_string(showerId_)]["ny"] = static_cast<double>(ny); + summary_["shower_" + std::to_string(showerId_)]["nz"] = static_cast<double>(nz); + summary_["shower_" + std::to_string(showerId_)]["px"] = + static_cast<double>(px / 1_GeV); + summary_["shower_" + std::to_string(showerId_)]["py"] = + static_cast<double>(py / 1_GeV); + summary_["shower_" + std::to_string(showerId_)]["pz"] = + static_cast<double>(pz / 1_GeV); + summary_["shower_" + std::to_string(showerId_)]["time"] = primary.getTime() / 1_s; + summary_["shower_" + std::to_string(showerId_)]["slant_depth"] = + dX / (1_g / 1_cm / 1_cm); + + uint nSecondaries = 0; // Loop through secondaries auto particle = vS.begin(); while (particle != vS.end()) { - // Get the displacement of the secondary w.r.t. observation plane - Vector const dr_2nd = particle.getPosition() - obsPlane_.getPlane().getCenter(); + // Get the momentum of the secondary, write w.r.t. observation plane auto const p_2nd = particle.getMomentum(); *(output_.getWriter()) - << showerId_ << false << static_cast<int>(get_PDG(particle.getPID())) - << static_cast<float>(dr_2nd.dot(obsPlane_.getXAxis()) / 1_m) - << static_cast<float>(dr_2nd.dot(obsPlane_.getYAxis()) / 1_m) - << static_cast<float>(dr_2nd.dot(obsPlane_.getPlane().getNormal()) / 1_m) + << showerId_ << static_cast<int>(get_PDG(particle.getPID())) << static_cast<float>(p_2nd.dot(obsPlane_.getXAxis()) / 1_GeV) << static_cast<float>(p_2nd.dot(obsPlane_.getYAxis()) / 1_GeV) << static_cast<float>(p_2nd.dot(obsPlane_.getPlane().getNormal()) / 1_GeV) - << static_cast<double>(particle.getTime() / 1_ns) - << static_cast<float>(dX / (1_g / 1_cm / 1_cm)) << parquet::EndRow; + << parquet::EndRow; CORSIKA_LOG_INFO(" 2ndary: {}, E_kin {}", particle.getPID(), particle.getKineticEnergy()); ++particle; - ++nSecondaries_; + ++nSecondaries; } + + summary_["shower_" + std::to_string(showerId_)]["n_secondaries"] = nSecondaries; } template <typename TTracking, typename TOutput> @@ -80,32 +102,19 @@ namespace corsika { boost::filesystem::path const& directory) { output_.initStreamer((directory / ("interactions.parquet")).string()); - output_.addField("primary", parquet::Repetition::REQUIRED, parquet::Type::BOOLEAN, - parquet::ConvertedType::NONE); output_.addField("pdg", parquet::Repetition::REQUIRED, parquet::Type::INT32, parquet::ConvertedType::INT_32); - 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); output_.addField("px", parquet::Repetition::REQUIRED, parquet::Type::FLOAT, parquet::ConvertedType::NONE); output_.addField("py", parquet::Repetition::REQUIRED, parquet::Type::FLOAT, parquet::ConvertedType::NONE); output_.addField("pz", parquet::Repetition::REQUIRED, parquet::Type::FLOAT, parquet::ConvertedType::NONE); - output_.addField("time", parquet::Repetition::REQUIRED, parquet::Type::DOUBLE, - parquet::ConvertedType::NONE); - output_.addField("dX", parquet::Repetition::REQUIRED, parquet::Type::FLOAT, - parquet::ConvertedType::NONE); output_.buildStreamer(); showerId_ = 0; interactionCounter_ = 0; - nSecondaries_ = 0; summary_ = YAML::Node(); } @@ -114,13 +123,10 @@ namespace corsika { unsigned int const showerId) { showerId_ = showerId; interactionCounter_ = 0; - nSecondaries_ = 0; } template <typename TTracking, typename TOutput> - inline void InteractionWriter<TTracking, TOutput>::endOfShower(unsigned int const) { - summary_["shower_" + std::to_string(showerId_)]["n_secondaries"] = nSecondaries_; - } + inline void InteractionWriter<TTracking, TOutput>::endOfShower(unsigned int const) {} template <typename TTracking, typename TOutput> inline void InteractionWriter<TTracking, TOutput>::endOfLibrary() { diff --git a/corsika/modules/writers/InteractionWriter.hpp b/corsika/modules/writers/InteractionWriter.hpp index 47179405d5c8c5936b4bb10a3e1ffe9b21315792..86527001f7a169df3924e200c7aee48ca4843b95 100644 --- a/corsika/modules/writers/InteractionWriter.hpp +++ b/corsika/modules/writers/InteractionWriter.hpp @@ -57,7 +57,6 @@ namespace corsika { unsigned int interactionCounter_; unsigned int showerId_; - unsigned int nSecondaries_; ParquetStreamer output_; YAML::Node summary_; diff --git a/corsika/output/OutputManager.hpp b/corsika/output/OutputManager.hpp index 6befc54ff741c55e63030237bdff2542d0312b81..1359b0d959322460ea9ab3f49621d74e9cfac6a1 100644 --- a/corsika/output/OutputManager.hpp +++ b/corsika/output/OutputManager.hpp @@ -35,8 +35,10 @@ namespace corsika { /** * Construct an OutputManager instance with a name in a given directory. * - * @param name The name of this output collection. - * @param dir The directory where the output directory will be stored. + * @param name The name of this output collection. + * @param vseed The seed for the simulation (written to summary file) + * @param input_args The command line arguments at runtime (written to summary file) + * @param dir The directory where the output directory will be stored. */ OutputManager(std::string const& name, const long& vseed, std::string const& input_args, boost::filesystem::path const& dir); diff --git a/python/corsika/io/outputs/interaction.py b/python/corsika/io/outputs/interaction.py index 3a3d83da3070bf7fe1332f2071e4e23c355eac6e..e721243a849151aa67c122cb055334f0b4074206 100644 --- a/python/corsika/io/outputs/interaction.py +++ b/python/corsika/io/outputs/interaction.py @@ -9,15 +9,45 @@ """ import logging +import pyarrow.parquet as pq import os.path as op from typing import Any +import yaml -import pyarrow.parquet as pq from ..converters import arrow_to_numpy from .output import Output +from .primary import Particle + +class FirstInteraction(Particle): + """ + Simple class that holds the information of the first interaction + This is a complete wrapper of the `Particle` class at the moment + but is "new" so that it can be distinguished using `type(thing)` + """ + def __init__(self, prop_dict: dict): + """ + Parameters + ---------- + prop_dict: dict + the entry from the output yaml file for a single shower + """ + Particle.__init__(self, prop_dict) + + def __repr__(self) -> str: + """ + Return a string representation of this class. + """ + out_str = "FirstInteraction:\n" + for key in self.__dict__.keys(): + out_str += f"\t{key}: {self.__dict__[key]}\n" + return out_str + @property + def momentum(self) -> list: + return [self.px, self.py, self.pz] + class Interactions(Output): """ Reads the interactions and secondaries @@ -37,11 +67,18 @@ class Interactions(Output): # try and load our data try: self.__data = pq.read_table(op.join(path, "interactions.parquet")) + with open(op.join(path, "summary.yaml"), "r") as f: + temp = yaml.load(f, Loader=yaml.Loader) + self.__projectiles = [FirstInteraction(x) for x in temp.values()] except Exception as e: logging.getLogger("corsika").warn( f"An error occured loading an Interaction: {e}" ) + @property + def projectiles(self) -> list: + return self.__projectiles + def is_good(self) -> bool: """ Returns true if this output has been read successfully diff --git a/python/examples/first_interactions.py b/python/examples/first_interactions.py index 95c30756a7ebeb80dc0253cbe749af863fe4c462..de433f1a5dd1043a1aca60f637ead3414473a18c 100644 --- a/python/examples/first_interactions.py +++ b/python/examples/first_interactions.py @@ -32,40 +32,50 @@ lib = corsika.Library(args.input_dir) # Load the primary particle information from the shower interactions = lib.get("interactions").data interaction_config = lib.get("interactions").config +projectiles = lib.get("interactions").projectiles shower_ids = np.unique(interactions["shower"]) -ncols = 1 +ncols = 2 nrows = len(shower_ids) fig, ax = plt.subplots( ncols=ncols, nrows=nrows, - figsize=(ncols * 8, nrows * 3), + figsize=(ncols * 8, nrows * 8), gridspec_kw={"wspace": 0.2, "hspace": 0.3}, ) -ax = np.atleast_1d(ax).flatten() +ax = np.atleast_2d(ax) +if ax.shape == (1, 2): + ax = ax.T -for sh_id in shower_ids: - data = interactions[interactions["shower"] == sh_id] +for ish, sh_id in enumerate(shower_ids): - mother = data[data["primary"]] - norm = np.sqrt(mother["px"] ** 2 + mother["py"] ** 2 + mother["pz"] ** 2) - angle = np.arctan2(mother["py"].iloc[0], mother["px"].iloc[0]) - pz = mother["pz"].iloc[0] - px = np.cos(-angle) * mother["px"].iloc[0] + np.sin(-angle) * mother["py"].iloc[0] + # Find all of the secondary particles associated with this shower + daughters = interactions[interactions["shower"] == sh_id] - pdg = int(mother["pdg"].iloc[0]) - name = f"${particle.Particle.from_pdgid(pdg).latex_name}$" - ax[sh_id].plot([-px, 0], [-pz, 0], label=name) + mother = projectiles[ish] + norm = np.sqrt(sum(np.array(mother.momentum)**2)) # total momentum + + print("Plotting shower", sh_id) + print(mother) + + px, py, pz = mother.momentum + + mother_name = f"${particle.Particle.from_pdgid(mother.pdg).latex_name}$" + ax[0, sh_id].plot([-px, 0], [-pz, 0], label=mother_name) + ax[1, sh_id].plot([-py, 0], [-pz, 0], label=mother_name) # Get daughters, filter out zero-momentum particles - daughters = data[data["primary"] is False] p_tot = np.sqrt(daughters["px"] ** 2 + daughters["py"] ** 2 + daughters["pz"] ** 2) daughters = daughters[p_tot > 0] + max_pz = abs(pz) + max_pxy = max(abs(px), abs(py)) + for i in range(len(daughters)): pz = daughters["pz"].iloc[i] - pt = np.sqrt(daughters["px"].iloc[i] ** 2 + daughters["py"].iloc[i] ** 2) + px = daughters["px"].iloc[i] + py = daughters["py"].iloc[i] pdg = int(daughters["pdg"].iloc[i]) try: @@ -73,16 +83,29 @@ for sh_id in shower_ids: except Exception: name = "Unknown" - ax[sh_id].plot([pt, 0], [pz, 0], label=name) + ax[0, sh_id].plot([px, 0], [pz, 0], label=name) + ax[1, sh_id].plot([py, 0], [pz, 0], label=name) + + max_pz = max(max_pz, abs(pz)) + max_pxy = max(max_pxy, max(abs(px), abs(py))) + + + for i in range(2): + ax[i, sh_id].legend() + + ymin, ymax = ax[i, sh_id].get_ylim() + ax[i, sh_id].set_ylim(-max_pz, max_pz) + ax[i, sh_id].set_xlim(-max_pxy, max_pxy) - ax[sh_id].set_aspect("equal") - ax[sh_id].legend() + unit_energy_str = interaction_config["units"]["energy"] + title = f"Mother: {mother.kinetic_energy:0.2e} {unit_energy_str}" + title += ", " + mother_name + title += f"\n{mother.n_secondaries} secondaries" + ax[i, sh_id].set_title(title) + ax[i, sh_id].set_ylabel(f"$P_z$ ({unit_energy_str})") - ymin, ymax = ax[sh_id].get_ylim() - xmin, xmax = ax[sh_id].get_xlim() - maxVal = max(ymin, ymax, xmin, xmax) - ax[sh_id].set_ylim(-maxVal, maxVal) - ax[sh_id].set_xlim(-maxVal, maxVal) + ax[0, sh_id].set_xlabel(f"$P_x$ ({unit_energy_str})") + ax[1, sh_id].set_xlabel(f"$P_y$ ({unit_energy_str})") plot_path = os.path.join(args.output_dir, "interactions.png") print("Saving", plot_path)