diff --git a/corsika/detail/modules/writers/EnergyLossWriter.inl b/corsika/detail/modules/writers/EnergyLossWriter.inl
new file mode 100644
index 0000000000000000000000000000000000000000..1fbf3b5ba2c961bb4f61ea39f1647362d5c83d3e
--- /dev/null
+++ b/corsika/detail/modules/writers/EnergyLossWriter.inl
@@ -0,0 +1,218 @@
+/*
+ * (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/framework/core/ParticleProperties.hpp>
+#include <corsika/framework/core/PhysicalUnits.hpp>
+#include <corsika/framework/utility/FindXmax.hpp>
+
+#include <corsika/media/ShowerAxis.hpp>
+
+#include <exception>
+
+namespace corsika {
+
+  template <typename TOutput>
+  inline EnergyLossWriter<TOutput>::EnergyLossWriter(ShowerAxis const& axis,
+                                                     GrammageType dX,
+                                                     unsigned int const nBins,
+                                                     GrammageType dX_threshold)
+      : showerAxis_(axis)
+      , dX_(dX)
+      , nBins_(nBins)
+      , dX_threshold_(dX_threshold) {}
+
+  template <typename TOutput>
+  inline void EnergyLossWriter<TOutput>::startOfLibrary(
+      boost::filesystem::path const& directory) {
+    TOutput::startOfLibrary(directory);
+  }
+
+  template <typename TOutput>
+  inline void EnergyLossWriter<TOutput>::startOfShower(unsigned int const showerId) {
+    TOutput::startOfShower(showerId);
+  }
+
+  template <typename TOutput>
+  inline void EnergyLossWriter<TOutput>::endOfLibrary() {
+    TOutput::endOfLibrary();
+  }
+
+  template <typename TOutput>
+  inline void EnergyLossWriter<TOutput>::endOfShower(unsigned int const showerId) {
+
+    int iRow{0};
+    for (Profile const& row : profile_) {
+      TOutput::write(showerId, iRow * dX_, row.at(static_cast<int>(ProfileIndex::Total)));
+      iRow++;
+    }
+
+    TOutput::endOfShower(showerId);
+
+    // reset profile
+    profile_.clear();
+    profile_.resize(nBins_);
+  }
+
+  template <typename TOutput>
+  template <typename TTrack>
+  inline void EnergyLossWriter<TOutput>::write(TTrack const& track, Code const PID,
+                                               HEPEnergyType const dE) {
+
+    GrammageType grammageStart = showerAxis_.getProjectedX(track.getPosition(0));
+    GrammageType grammageEnd = showerAxis_.getProjectedX(track.getPosition(1));
+
+    if (grammageStart > grammageEnd) { // particle going upstream
+      std::swap(grammageStart, grammageEnd);
+    }
+
+    GrammageType const deltaX = grammageEnd - grammageStart;
+
+    if (deltaX < dX_threshold_) {
+      this->write(track.getPosition(0), PID, dE);
+      return;
+    }
+
+    // only register the range that is covered by the profile
+    int const maxBin = int(profile_.size() - 1);
+    int binStart = grammageStart / dX_;
+    if (binStart < 0) binStart = 0;
+    if (binStart > maxBin) binStart = maxBin;
+    int binEnd = grammageEnd / dX_;
+    if (binEnd < 0) binEnd = 0;
+    if (binEnd > maxBin) binEnd = maxBin;
+
+    CORSIKA_LOGGER_TRACE(
+        TOutput::getLogger(), "energy deposit of dE={} GeV between {} and {}", dE / 1_GeV,
+        grammageStart / 1_g * square(1_cm), grammageEnd / 1_g * square(1_cm));
+
+    auto energyCount = HEPEnergyType::zero();
+
+    auto const factor = dE / deltaX;
+    auto fill = [&](int const bin, GrammageType const weight) {
+      auto const increment = factor * weight;
+      profile_[bin][static_cast<int>(ProfileIndex::Total)] += increment;
+      energyCount += increment;
+
+      CORSIKA_LOGGER_TRACE(TOutput::getLogger(),
+                           "filling bin={} with weight {} : dE={} GeV ", bin, weight,
+                           increment / 1_GeV);
+    };
+
+    // fill longitudinal profile
+    if (binStart == binEnd) {
+      fill(binStart, deltaX);
+    } else {
+      fill(binStart, ((1 + binStart) * dX_ - grammageStart));
+      fill(binEnd, (grammageEnd - binEnd * dX_));
+      for (int bin = binStart + 1; bin < binEnd; ++bin) { fill(bin, dX_); }
+    }
+
+    CORSIKA_LOGGER_TRACE(TOutput::getLogger(), "total energy added to histogram: {} GeV ",
+                         energyCount / 1_GeV);
+  }
+
+  template <typename TOutput>
+  inline void EnergyLossWriter<TOutput>::write(Point const& point, Code const PID,
+                                               HEPEnergyType const dE) {
+    GrammageType grammage = showerAxis_.getProjectedX(point);
+    int const maxBin = int(profile_.size() - 1);
+    int bin = grammage / dX_;
+    if (bin < 0) bin = 0;
+    if (bin > maxBin) bin = maxBin;
+
+    CORSIKA_LOGGER_TRACE(TOutput::getLogger(), "add local energy loss bin={} dE={} GeV ",
+                         bin, dE / 1_GeV);
+
+    profile_[bin][static_cast<int>(ProfileIndex::Total)] += dE;
+  }
+
+  template <typename TOutput>
+  inline void EnergyLossWriter<TOutput>::write(GrammageType const Xstart,
+                                               GrammageType const Xend,
+                                               HEPEnergyType const dE) {
+    double const bstart = Xstart / dX_;
+    double const bend = Xend / dX_;
+
+    if (abs(bstart - floor(bstart + 0.5)) > 1e-2 ||
+        abs(bend - floor(bend + 0.5)) > 1e-2 || abs(bend - bstart - 1) > 1e-2) {
+      CORSIKA_LOGGER_ERROR(TOutput::getLogger(),
+                           "CONEX and Corsika8 dX grammage binning are not the same! "
+                           "Xstart={} Xend={} dX={}",
+                           Xstart / 1_g * square(1_cm), Xend / 1_g * square(1_cm),
+                           dX_ / 1_g * square(1_cm));
+      throw std::runtime_error(
+          "CONEX and Corsika8 dX grammage binning are not the same!");
+    }
+
+    int const bin = int((bend + bstart) / 2);
+    CORSIKA_LOGGER_TRACE(TOutput::getLogger(),
+                         "add binned energy loss {} {} bin={} dE={} GeV ", bstart, bend,
+                         bin, dE / 1_GeV);
+    profile_[bin][static_cast<int>(ProfileIndex::Total)] += dE;
+  }
+
+  template <typename TOutput>
+  inline HEPEnergyType EnergyLossWriter<TOutput>::getTotal() const {
+    HEPEnergyType tot = HEPEnergyType::zero();
+    for (Profile const& row : profile_)
+      tot += row.at(static_cast<int>(ProfileIndex::Total));
+    return tot;
+  }
+
+  template <typename TOutput>
+  inline YAML::Node EnergyLossWriter<TOutput>::getConfig() const {
+
+    YAML::Node node;
+
+    node["type"] = "EnergyLoss";
+    node["units"]["energy"] = "GeV";
+    node["units"]["grammage"] = "g/cm^2";
+    node["bin-size"] = dX_ / (1_g / square(1_cm));
+    node["nbins"] = nBins_;
+    node["grammage_threshold"] = dX_threshold_ / (1_g / square(1_cm));
+
+    // TODO: add shower axis to config
+
+    return node;
+  }
+
+  template <typename TOutput>
+  inline YAML::Node EnergyLossWriter<TOutput>::getSummary() const {
+
+    // determined Xmax and dEdXmax from quadratic interpolation
+    double maximum = 0;
+    unsigned int iMaximum = 0;
+    for (unsigned int i = 0; i < profile_.size() - 3; ++i) {
+      double value = (profile_[i + 0].at(static_cast<int>(ProfileIndex::Total)) +
+                      profile_[i + 1].at(static_cast<int>(ProfileIndex::Total)) +
+                      profile_[i + 2].at(static_cast<int>(ProfileIndex::Total))) /
+                     1_GeV;
+      if (value > maximum) {
+        maximum = value;
+        iMaximum = i;
+      }
+    }
+
+    double const dX = dX_ / 1_g * square(1_cm);
+
+    auto [Xmax, dEdXmax] = FindXmax::interpolateProfile(
+        dX * (0.5 + iMaximum), dX * (1.5 + iMaximum), dX * (2.5 + iMaximum),
+        profile_[iMaximum + 0].at(static_cast<int>(ProfileIndex::Total)) / 1_GeV,
+        profile_[iMaximum + 1].at(static_cast<int>(ProfileIndex::Total)) / 1_GeV,
+        profile_[iMaximum + 2].at(static_cast<int>(ProfileIndex::Total)) / 1_GeV);
+
+    YAML::Node summary;
+    summary["total"] = getTotal() / 1_GeV;
+    summary["Xmax"] = Xmax;
+    summary["dEdXmax"] = dEdXmax;
+    return summary;
+  }
+
+} // namespace corsika
diff --git a/corsika/detail/modules/writers/EnergyLossWriterParquet.inl b/corsika/detail/modules/writers/EnergyLossWriterParquet.inl
index 7148bdf8e43b01c77c7ba23503d29ea70a5bdf26..b7e82d669747150dd9ba73b7d1e21f939cef253d 100644
--- a/corsika/detail/modules/writers/EnergyLossWriterParquet.inl
+++ b/corsika/detail/modules/writers/EnergyLossWriterParquet.inl
@@ -18,14 +18,7 @@
 
 namespace corsika {
 
-  inline EnergyLossWriterParquet::EnergyLossWriterParquet(ShowerAxis const& showerAxis,
-                                                          GrammageType const dX,
-                                                          unsigned int const nBins,
-                                                          GrammageType const dX_threshold)
-      : showerAxis_(showerAxis)
-      , dX_(dX) // profile binning
-      , nBins_(nBins)
-      , dX_threshold_(dX_threshold) {}
+  inline EnergyLossWriterParquet::EnergyLossWriterParquet() {}
 
   inline void EnergyLossWriterParquet::startOfLibrary(
       boost::filesystem::path const& directory) {
@@ -46,160 +39,21 @@ namespace corsika {
     output_.buildStreamer();
   }
 
-  inline void EnergyLossWriterParquet::startOfShower(unsigned int const) {
+  inline void EnergyLossWriterParquet::write(unsigned int const showerId,
+                                             GrammageType const grammage,
+                                             HEPEnergyType const total) {
 
-    // initialize profile
-    profile_.clear();
-    profile_.resize(nBins_);
-  }
-
-  inline void EnergyLossWriterParquet::endOfShower(unsigned int const showerId) {
-
-    int iRow = 0;
-    for (Profile const& row : profile_) {
-
-      double const dX = dX_ / 1_g * square(1_cm); // g/cm2
-
-      // clang-format off
-      *(output_.getWriter()) << showerId << static_cast<float>(iRow * dX)
-                             << static_cast<float>(row.at(static_cast<int>(ProfileIndex::Total)) / 1_GeV / dX)
-                             << parquet::EndRow;
-      // clang-format on
-      iRow++;
-    }
-  }
-
-  inline void EnergyLossWriterParquet::endOfLibrary() { output_.closeStreamer(); }
+    double const dX = grammage / 1_g * square(1_cm); // g/cm2
 
-  template <typename TTrack>
-  inline void EnergyLossWriterParquet::write(TTrack const& track, Code const PID,
-                                             HEPEnergyType const dE) {
-
-    GrammageType grammageStart = showerAxis_.getProjectedX(track.getPosition(0));
-    GrammageType grammageEnd = showerAxis_.getProjectedX(track.getPosition(1));
-
-    if (grammageStart > grammageEnd) { // particle going upstream
-      std::swap(grammageStart, grammageEnd);
-    }
-
-    GrammageType const deltaX = grammageEnd - grammageStart;
-
-    if (deltaX < dX_threshold_) {
-      write(track.getPosition(0), PID, dE);
-      return;
-    }
-
-    // only register the range that is covered by the profile
-    int const maxBin = int(profile_.size() - 1);
-    int binStart = grammageStart / dX_;
-    if (binStart < 0) binStart = 0;
-    if (binStart > maxBin) binStart = maxBin;
-    int binEnd = grammageEnd / dX_;
-    if (binEnd < 0) binEnd = 0;
-    if (binEnd > maxBin) binEnd = maxBin;
-
-    CORSIKA_LOGGER_TRACE(getLogger(), "energy deposit of dE={} GeV between {} and {}",
-                         dE / 1_GeV, grammageStart / 1_g * square(1_cm),
-                         grammageEnd / 1_g * square(1_cm));
-
-    auto energyCount = HEPEnergyType::zero();
-
-    auto const factor = dE / deltaX;
-    auto fill = [&](int const bin, GrammageType const weight) {
-      auto const increment = factor * weight;
-      profile_[bin][static_cast<int>(ProfileIndex::Total)] += increment;
-      energyCount += increment;
-
-      CORSIKA_LOGGER_TRACE(getLogger(), "filling bin={} with weight {} : dE={} GeV ", bin,
-                           weight, increment / 1_GeV);
-    };
-
-    // fill longitudinal profile
-    if (binStart == binEnd) {
-      fill(binStart, deltaX);
-    } else {
-      fill(binStart, ((1 + binStart) * dX_ - grammageStart));
-      fill(binEnd, (grammageEnd - binEnd * dX_));
-      for (int bin = binStart + 1; bin < binEnd; ++bin) { fill(bin, dX_); }
-    }
-
-    CORSIKA_LOGGER_TRACE(getLogger(), "total energy added to histogram: {} GeV ",
-                         energyCount / 1_GeV);
+    // and write the data into the column
+    *(output_.getWriter()) << showerId << static_cast<float>(dX)
+                           << static_cast<float>(total / 1_GeV) << parquet::EndRow;
   }
 
-  inline void EnergyLossWriterParquet::write(Point const& point, Code const PID,
-                                             HEPEnergyType const dE) {
-    GrammageType grammage = showerAxis_.getProjectedX(point);
-    int const maxBin = int(profile_.size() - 1);
-    int bin = grammage / dX_;
-    if (bin < 0) bin = 0;
-    if (bin > maxBin) bin = maxBin;
+  inline void EnergyLossWriterParquet::startOfShower(unsigned int const) {}
 
-    CORSIKA_LOGGER_TRACE(getLogger(), "add local energy loss bin={} dE={} GeV ", bin,
-                         dE / 1_GeV);
+  inline void EnergyLossWriterParquet::endOfShower(unsigned int const showerId) {}
 
-    profile_[bin][static_cast<int>(ProfileIndex::Total)] += dE;
-  }
-
-  inline void EnergyLossWriterParquet::write(GrammageType const Xstart,
-                                             GrammageType const Xend,
-                                             HEPEnergyType const dE) {
-    double const bstart = Xstart / dX_;
-    double const bend = Xend / dX_;
-
-    if (abs(bstart - floor(bstart + 0.5)) > 1e-2 ||
-        abs(bend - floor(bend + 0.5)) > 1e-2 || abs(bend - bstart - 1) > 1e-2) {
-      CORSIKA_LOGGER_ERROR(getLogger(),
-                           "CONEX and Corsika8 dX grammage binning are not the same! "
-                           "Xstart={} Xend={} dX={}",
-                           Xstart / 1_g * square(1_cm), Xend / 1_g * square(1_cm),
-                           dX_ / 1_g * square(1_cm));
-      throw std::runtime_error(
-          "CONEX and Corsika8 dX grammage binning are not the same!");
-    }
-
-    int const bin = int((bend + bstart) / 2);
-    CORSIKA_LOGGER_TRACE(getLogger(), "add binned energy loss {} {} bin={} dE={} GeV ",
-                         bstart, bend, bin, dE / 1_GeV);
-    profile_[bin][static_cast<int>(ProfileIndex::Total)] += dE;
-  }
-
-  inline HEPEnergyType EnergyLossWriterParquet::getTotal() const {
-    HEPEnergyType tot = HEPEnergyType::zero();
-    for (Profile const& row : profile_)
-      tot += row.at(static_cast<int>(ProfileIndex::Total));
-    return tot;
-  }
-
-  inline YAML::Node EnergyLossWriterParquet::getSummary() const {
-
-    // determined Xmax and dEdXmax from quadratic interpolation
-    double maximum = 0;
-    unsigned int iMaximum = 0;
-    for (unsigned int i = 0; i < profile_.size() - 3; ++i) {
-      double value = (profile_[i + 0].at(static_cast<int>(ProfileIndex::Total)) +
-                      profile_[i + 1].at(static_cast<int>(ProfileIndex::Total)) +
-                      profile_[i + 2].at(static_cast<int>(ProfileIndex::Total))) /
-                     1_GeV;
-      if (value > maximum) {
-        maximum = value;
-        iMaximum = i;
-      }
-    }
-
-    double const dX = dX_ / 1_g * square(1_cm);
-
-    auto [Xmax, dEdXmax] = FindXmax::interpolateProfile(
-        dX * (0.5 + iMaximum), dX * (1.5 + iMaximum), dX * (2.5 + iMaximum),
-        profile_[iMaximum + 0].at(static_cast<int>(ProfileIndex::Total)) / 1_GeV,
-        profile_[iMaximum + 1].at(static_cast<int>(ProfileIndex::Total)) / 1_GeV,
-        profile_[iMaximum + 2].at(static_cast<int>(ProfileIndex::Total)) / 1_GeV);
-
-    YAML::Node summary;
-    summary["total"] = getTotal() / 1_GeV;
-    summary["Xmax"] = Xmax;
-    summary["dEdXmax"] = dEdXmax;
-    return summary;
-  }
+  inline void EnergyLossWriterParquet::endOfLibrary() { output_.closeStreamer(); }
 
 } // namespace corsika
diff --git a/corsika/modules/writers/EnergyLossWriter.hpp b/corsika/modules/writers/EnergyLossWriter.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8806acd8c5bc0330fc80243f9d95a06e5c8368d6
--- /dev/null
+++ b/corsika/modules/writers/EnergyLossWriter.hpp
@@ -0,0 +1,92 @@
+/*
+ * (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>
+#include <corsika/media/ShowerAxis.hpp>
+#include <corsika/modules/writers/WriterOff.hpp>
+
+#include <vector>
+#include <array>
+
+namespace corsika {
+
+  template <typename TOutput = WriterOff>
+  class EnergyLossWriter : public TOutput {
+
+    enum class ProfileIndex { Total, Entries };
+    typedef std::array<HEPEnergyType, static_cast<int>(ProfileIndex::Entries)> Profile;
+
+  public:
+    /**
+     * Construct a new writer.
+     */
+    EnergyLossWriter(ShowerAxis const& axis,
+                     GrammageType dX = 10_g / square(1_cm), // profile binning
+                     unsigned int const nBins = 200,        // number of bins
+                     GrammageType dX_threshold = 0.0001_g /
+                                                 square(1_cm)); // ignore too short tracks
+
+    void startOfLibrary(boost::filesystem::path const& directory) final override;
+
+    void startOfShower(unsigned int const showerId) final override;
+
+    void endOfShower(unsigned int const showerId) final override;
+
+    void endOfLibrary() final override;
+
+    /**
+     * Add continuous energy loss.
+     */
+    template <typename TTrack>
+    void write(TTrack const& track, Code const PID, HEPEnergyType const dE);
+
+    /**
+     * Add localized energy loss.
+     */
+    void write(Point const& point, Code const PID, HEPEnergyType const dE);
+
+    /**
+     * Add binned energy loss.
+     */
+    void write(GrammageType const Xstart, GrammageType const Xend,
+               HEPEnergyType const dE);
+
+    /**
+     * Get total observed energy loss.
+     *
+     * @return HEPEnergyType The total energy.
+     */
+    HEPEnergyType getTotal() const;
+
+    /**
+     * Return a summary.
+     */
+    YAML::Node getSummary() const;
+
+    /**
+     * Return the configuration of this output.
+     */
+    YAML::Node getConfig() const;
+
+  private:
+    ShowerAxis const& showerAxis_; ///< conversion between geometry and grammage
+    GrammageType dX_;              ///< binning of profile.
+    unsigned int nBins_;           ///< number of profile bins.
+    GrammageType dX_threshold_;    ///< too short tracks are discarded.
+    std::vector<Profile> profile_; // longitudinal profile
+
+  }; // namespace corsika
+
+} // namespace corsika
+
+#include <corsika/detail/modules/writers/EnergyLossWriter.inl>
diff --git a/corsika/modules/writers/EnergyLossWriterParquet.hpp b/corsika/modules/writers/EnergyLossWriterParquet.hpp
index b200c8deb5177ce763539b9f6e8b9b202cfec8ca..5f2cbac28d1f389aa49a7b91dcb81918ad60acc3 100644
--- a/corsika/modules/writers/EnergyLossWriterParquet.hpp
+++ b/corsika/modules/writers/EnergyLossWriterParquet.hpp
@@ -21,33 +21,26 @@ namespace corsika {
 
   class EnergyLossWriterParquet : public BaseOutput {
 
-    enum class ProfileIndex { Total, Entries };
-    typedef std::array<HEPEnergyType, static_cast<int>(ProfileIndex::Entries)> Profile;
-
   public:
     /**
      * Construct a new writer.
      */
-    EnergyLossWriterParquet(
-        ShowerAxis const& axis,
-        GrammageType dX = 10_g / square(1_cm),                // profile binning
-        unsigned int const nBins = 200,                       // number of bins
-        GrammageType dX_threshold = 0.0001_g / square(1_cm)); // ignore too short tracks
+    EnergyLossWriterParquet();
 
     /**
      * Called at the start of each library.
      */
-    void startOfLibrary(boost::filesystem::path const& directory) final override;
+    void startOfLibrary(boost::filesystem::path const& directory) override;
 
     /**
      * Called at the start of each shower.
      */
-    void startOfShower(unsigned int const showerId) final override;
+    void startOfShower(unsigned int const showerId) override;
 
     /**
      * Called at the end of each shower.
      */
-    void endOfShower(unsigned int const showerId) final override;
+    void endOfShower(unsigned int const showerId) override;
 
     /**
      * Called at the end of each library.
@@ -55,46 +48,17 @@ namespace corsika {
      * This must also increment the run number since we override
      * the default behaviour of BaseOutput.
      */
-    void endOfLibrary() final override;
-
-    /**
-     * Add continuous energy loss.
-     */
-    template <typename TTrack>
-    void write(TTrack const& track, Code const PID, HEPEnergyType const dE);
-
-    /**
-     * Add localized energy loss.
-     */
-    void write(Point const& point, Code const PID, HEPEnergyType const dE);
-
-    /**
-     * Add binned energy loss.
-     */
-    void write(GrammageType const Xstart, GrammageType const Xend,
-               HEPEnergyType const dE);
-
-    /**
-     * Get total observed energy loss.
-     *
-     * @return HEPEnergyType The total energy.
-     */
-    HEPEnergyType getTotal() const;
+    void endOfLibrary() override;
 
     /**
-     * Returns library-wide summary.
+     * Write energy lost to the file
      */
-    YAML::Node getSummary() const;
+    void write(unsigned int const showerId, GrammageType const grammage,
+               HEPEnergyType const total);
 
-  public:
+  private:
     ParquetStreamer output_; ///< The primary output file.
 
-    ShowerAxis const& showerAxis_; ///< conversion between geometry and grammage
-    GrammageType dX_;              ///< binning of profile.
-    unsigned int nBins_;           ///< number of profile bins.
-    GrammageType dX_threshold_;    ///< too short tracks are discarded.
-    std::vector<Profile> profile_; // longitudinal profile
-
   }; // namespace corsika
 
 } // namespace corsika
diff --git a/corsika/modules/writers/SubWriter.hpp b/corsika/modules/writers/SubWriter.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d75017715114c97f9b845b4caeb63b8fbeae94cd
--- /dev/null
+++ b/corsika/modules/writers/SubWriter.hpp
@@ -0,0 +1,38 @@
+/*
+ * (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/Configurable.hpp>
+
+#include <utility>
+
+namespace corsika {
+
+  template <typename TOutput>
+  class SubWriter : public Configurable {
+
+    TOutput& output_;
+
+  public:
+    SubWriter(TOutput& output)
+        : output_(output) {}
+
+    virtual ~SubWriter() {}
+
+    /*
+     * Write data to the underlying writer.
+     */
+    template <typename... TArgs>
+    void write(TArgs&&... args) {
+      output_.write(std::forward<TArgs>(args)...);
+    }
+
+  }; // class SubWriter
+
+} // namespace corsika
diff --git a/examples/vertical_EAS.cpp b/examples/vertical_EAS.cpp
index 2023d527bd48ffda3af586c6d1cc3721b3b82ced..a8ee6af6f30226424ddf59eb4af3228158d339cd 100644
--- a/examples/vertical_EAS.cpp
+++ b/examples/vertical_EAS.cpp
@@ -41,9 +41,9 @@
 #include <corsika/media/ShowerAxis.hpp>
 #include <corsika/media/CORSIKA7Atmospheres.hpp>
 
-// #include <corsika/modules/writers/SubWriter.hpp>
-// #include <corsika/modules/writers/EnergyLossWriter.hpp>
-// #include <corsika/modules/writers/EnergyLossWriterParquet.hpp>
+#include <corsika/modules/writers/SubWriter.hpp>
+#include <corsika/modules/writers/EnergyLossWriter.hpp>
+#include <corsika/modules/writers/EnergyLossWriterParquet.hpp>
 #include <corsika/modules/BetheBlochPDG.hpp>
 #include <corsika/modules/writers/BetheBlochPDGWriterParquet.hpp>
 #include <corsika/modules/LongitudinalProfile.hpp>
@@ -203,13 +203,23 @@ int main(int argc, char** argv) {
   // output.add("dEdX", dEdX_output);
   // register profile output
 
+  // construct the overall energy loss writer and register it
+  EnergyLossWriter<EnergyLossWriterParquet> dEdX{showerAxis};
+  output.add("energyloss", dEdX);
+
+  // construct the continuous energy loss model
+  BetheBlochPDG<SubWriter<decltype(dEdX)>> emContinuous{dEdX};
+  // output.add("bethebloch", emContinuous);
+
+  // construct a particle cut
+  ParticleCut<SubWriter<decltype(dEdX)>> cut{60_GeV, 60_GeV, 60_GeV, 60_GeV, true, dEdX};
+  // output.add("cuts", cut);
+
   // setup longitudinal profile
-  LongitudinalProfile<LongitudinalProfileWriterParquet> profile{showerAxis};
+  LongitudinalProfile<corsika::LongitudinalProfileWriterParquet> profile{showerAxis};
   output.add("profile", profile);
 
-  // register ground particle output
-  // ParticleWriterParquet particleOutput;
-
+  // create a track writer and register it with the output manager
   TrackWriter<TrackWriterParquet> trackWriter;
   output.add("tracks", trackWriter);
 
@@ -246,9 +256,6 @@ int main(int argc, char** argv) {
 
   decaySibyll.printDecayConfig();
 
-  ParticleCut<ParticleCutWriterParquet> cut{60_GeV, 60_GeV, 60_GeV, 60_GeV, true};
-  output.add("cuts", cut);
-
   corsika::urqmd::UrQMD urqmd;
   InteractionCounter urqmdCounted{urqmd};
   StackInspector<setup::Stack> stackInspect(50000, false, E0);
@@ -281,16 +288,11 @@ int main(int argc, char** argv) {
 										    obsPlane, DirectionVector(rootCS, {1., 0., 0.}), particleOutput};
   output.add("particles", observationLevel);
 
-  // EnergyLossWriter<EnergyLossWriterParquet> dEdX{showerAxis, 10_g / square(1_cm), 200};
-  BetheBlochPDG<BetheBlochPDGWriterParquet> emContinuous;
-  output.add("bethebloch", emContinuous);
-  // dEdX.add(emContinuous);
-
   // auto sequence = make_sequence(stackInspect, hadronSequence, decaySequence,
   // emContinuous,
   //                               cut, trackWriter, observationLevel, longprof);
   auto sequence = make_sequence(stackInspect, hadronSequence, decaySequence, emContinuous,
-                                cut, trackWriter, observationLevel);
+                                cut, trackWriter, observationLevel, profile);
 
   // define air shower object, run simulation
   setup::Tracking tracking;