diff --git a/.github/workflows/linux-eic-shell.yml b/.github/workflows/linux-eic-shell.yml index 6a91cd78ed..a5e645c72e 100644 --- a/.github/workflows/linux-eic-shell.yml +++ b/.github/workflows/linux-eic-shell.yml @@ -1321,6 +1321,94 @@ jobs: if-no-files-found: error include-hidden-files: true + eicrecon-timeframe-splitting: + runs-on: ubuntu-24.04 + needs: + - build + - npsim-dis + - npsim-minbias + strategy: + matrix: + include: + - CXX: clang++ + beam: 18x275 + minq2: 100 + detector_config: craterlake_18x275 + sanitizer: ASAN + + - CXX: clang++ + beam: 18x275 + minq2: 100 + detector_config: craterlake_18x275 + + sanitizer: TSAN + nthreads: 4 + + steps: + - name: Checkout .github and scripts + uses: actions/checkout@v7 + with: + sparse-checkout: | + .github + src/scripts + + - name: Download install directory + uses: actions/download-artifact@v8 + with: + name: install-${{ matrix.CXX }}-eic-shell-Release-${{ env.platform }}-${{ env.release }}-${{ matrix.sanitizer }} + + - name: Unarchive install directory + run: tar -xaf install.tar.zst + + - name: Download simulation input + uses: actions/download-artifact@v8 + with: + name: sim_dis_${{ matrix.beam }}_minQ2=${{ matrix.minq2 }}_${{ matrix.detector_config }}.edm4hep.rnt.root + + - name: Setup cvmfs + uses: cvmfs-contrib/github-action-cvmfs@v5 + + - name: Run EICrecon with timeframe splitting + uses: eic/run-cvmfs-osg-eic-shell@main + with: + organization: "${{ env.organization }}" + platform-release: "${{ env.platform }}:${{ env.release }}" + setup: "/opt/detector/epic-${{ env.detector-version }}/bin/thisepic.sh" + run: | + echo "::add-matcher::${{github.workspace}}/.github/ubsan.json" + echo "::add-matcher::${{github.workspace}}/.github/eicrecon.json" + + export DETECTOR_CONFIG=${DETECTOR}_${{ matrix.detector_config }} + export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH + export JANA_PLUGIN_PATH=$PWD/install/lib/EICrecon/plugins:/usr/local/plugins + + prmon --json-summary timeframe_splitter_${{matrix.sanitizer}}.prmon.json -- \ + $PWD/install/bin/eicrecon \ + ${{env.JANA_OPTIONS}} \ + ${{matrix.nthreads > 1 && format('-Pnthreads={0}', matrix.nthreads) || ''}} \ + -Ppodio:output_file=timeframe_splitter_${{matrix.sanitizer}}.edm4eic.root \ + sim_dis_${{matrix.beam}}_minQ2=${{matrix.minq2}}_${{ matrix.detector_config }}.edm4hep.rnt.root \ + -Psplit_timeframes=true + + - name: Verify output file + run: | + test -s timeframe_splitter_${{matrix.sanitizer}}.edm4eic.root + + - name: Upload output + uses: actions/upload-artifact@v7 + with: + name: timeframe_splitter_${{matrix.sanitizer}}.edm4eic.root + path: timeframe_splitter_${{matrix.sanitizer}}.edm4eic.root + if-no-files-found: error + + - name: Upload prmon report + uses: actions/upload-artifact@v7 + with: + name: timeframe_splitter_${{matrix.sanitizer}}.prmon.json + path: timeframe_splitter_${{matrix.sanitizer}}.prmon.json + if-no-files-found: error + + compare-single-multi-threaded: runs-on: ubuntu-24.04 needs: diff --git a/src/algorithms/CMakeLists.txt b/src/algorithms/CMakeLists.txt index 385bf4ff5c..50a1b4ca67 100644 --- a/src/algorithms/CMakeLists.txt +++ b/src/algorithms/CMakeLists.txt @@ -9,3 +9,4 @@ add_subdirectory(digi) add_subdirectory(reco) add_subdirectory(fardetectors) add_subdirectory(onnx) +add_subdirectory(event_building) diff --git a/src/algorithms/event_building/CMakeLists.txt b/src/algorithms/event_building/CMakeLists.txt new file mode 100644 index 0000000000..3b5ca91e53 --- /dev/null +++ b/src/algorithms/event_building/CMakeLists.txt @@ -0,0 +1,8 @@ +set(PLUGIN_NAME "algorithms_event_building") + +# Function creates ${PLUGIN_NAME}_plugin and ${PLUGIN_NAME}_library targets +# Setting default includes, libraries and installation paths +plugin_headers_only(${PLUGIN_NAME}) + +# Find dependencies +plugin_add_algorithms(${PLUGIN_NAME}) diff --git a/src/algorithms/event_building/HitTimeAlignment.h b/src/algorithms/event_building/HitTimeAlignment.h new file mode 100644 index 0000000000..24f79e1f3a --- /dev/null +++ b/src/algorithms/event_building/HitTimeAlignment.h @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright (C) 2026 Takuya Kumaoka + +#pragma once + +#include +#include +#include +#include + +#include + +#include "algorithms/interfaces/WithPodConfig.h" +#include "algorithms/event_building/HitTimeAlignmentConfig.h" + +namespace eicrecon { + +template +using HitTimeAlignmentAlgorithm = + algorithms::Algorithm, + algorithms::Output>; + +template +class HitTimeAlignment : public HitTimeAlignmentAlgorithm, + public WithPodConfig { +public: + using AlgorithmT = HitTimeAlignmentAlgorithm; + + explicit HitTimeAlignment(std::string_view name) + : AlgorithmT{name, + {"inputHits"}, + {"outputHits"}, + "Correct reconstructed hit times for propagation and sort by time."} {} + + void init() final {} + + void process(const typename AlgorithmT::Input& input, + const typename AlgorithmT::Output& output) const final { + const auto [hits_in] = input; + auto [hits_out] = output; + + std::vector sortedHits; + sortedHits.reserve(hits_in->size()); + for (const auto& hit : *hits_in) { + auto copiedHit = hit.clone(); + const auto position = hit.getPosition(); + const auto radius = std::sqrt(position[0] * position[0] + position[1] * position[1] + + position[2] * position[2]); + const auto aveTimeOfFlight = radius * this->m_cfg.refInverseVelocity; + copiedHit.setTime(hit.getTime() - aveTimeOfFlight); + sortedHits.push_back(copiedHit); + } + + std::stable_sort(sortedHits.begin(), sortedHits.end(), [](const auto& lhs, const auto& rhs) { + return lhs.getTime() < rhs.getTime(); + }); + + for (const auto& hit : sortedHits) { + hits_out->push_back(hit); + } + } +}; + +} // namespace eicrecon diff --git a/src/algorithms/event_building/HitTimeAlignmentConfig.h b/src/algorithms/event_building/HitTimeAlignmentConfig.h new file mode 100644 index 0000000000..8fa85e9bde --- /dev/null +++ b/src/algorithms/event_building/HitTimeAlignmentConfig.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright (C) 2026 Takuya Kumaoka + +#pragma once + +namespace eicrecon { + +struct HitTimeAlignmentConfig { + double refInverseVelocity = + 0.0034; //< ns/mm estimated by MC average time of flight / distance from IP to calorimeter +}; + +} // namespace eicrecon diff --git a/src/detectors/B0ECAL/B0ECAL.cc b/src/detectors/B0ECAL/B0ECAL.cc index 667b52ec0f..18f7023c5c 100644 --- a/src/detectors/B0ECAL/B0ECAL.cc +++ b/src/detectors/B0ECAL/B0ECAL.cc @@ -2,7 +2,9 @@ // Copyright (C) 2022 - 2025 Whitney Armstrong, Sylvester Joosten, Chao Peng, David Lawrence, Wouter Deconinck, Kolja Kauder, Nathan Brei, Dmitry Kalinkin, Derek Anderson, Michael Pitt #include +#include #include +#include #include #include #include @@ -23,6 +25,9 @@ void InitPlugin(JApplication* app) { using namespace eicrecon; InitJANAPlugin(app); + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + const auto hit_level = split_timeframes ? JEventLevel::Timeslice : JEventLevel::PhysicsEvent; app->Add(new JOmniFactoryGeneratorT( "B0ECalRawHits", {"EventHeader", "B0ECalHits"}, @@ -40,7 +45,7 @@ void InitPlugin(JApplication* app) { .corrMeanScale = "1.0", .readout = "B0ECalHits", }, - app)); + app, hit_level)); app->Add(new JOmniFactoryGeneratorT( "B0ECalRecHits", {"B0ECalRawHits"}, {"B0ECalRecHits"}, { @@ -55,7 +60,7 @@ void InitPlugin(JApplication* app) { .readout = "B0ECalHits", .sectorField = "sector", }, - app)); + app, hit_level)); app->Add(new JOmniFactoryGeneratorT( "B0ECalTruthProtoClusters", {"B0ECalRecHits", "B0ECalHits"}, {"B0ECalTruthProtoClusters"}, app)); diff --git a/src/detectors/B0TRK/B0TRK.cc b/src/detectors/B0TRK/B0TRK.cc index d7b53cc1fe..6e98776112 100644 --- a/src/detectors/B0TRK/B0TRK.cc +++ b/src/detectors/B0TRK/B0TRK.cc @@ -4,7 +4,9 @@ // #include +#include #include +#include #include #include #include @@ -18,6 +20,9 @@ void InitPlugin(JApplication* app) { InitJANAPlugin(app); using namespace eicrecon; + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + const auto hit_level = split_timeframes ? JEventLevel::Timeslice : JEventLevel::PhysicsEvent; // Digitization app->Add(new JOmniFactoryGeneratorT( @@ -27,7 +32,7 @@ void InitPlugin(JApplication* app) { .threshold = 10.0 * dd4hep::keV, .timeResolution = 8, }, - app)); + app, hit_level)); // Convert raw digitized hits into hits with geometry info (ready for tracking) app->Add(new JOmniFactoryGeneratorT( @@ -35,6 +40,6 @@ void InitPlugin(JApplication* app) { { .timeResolution = 8, }, - app)); + app, hit_level)); } } // extern "C" diff --git a/src/detectors/BEMC/BEMC.cc b/src/detectors/BEMC/BEMC.cc index 0628a86917..3a3aa8d9ed 100644 --- a/src/detectors/BEMC/BEMC.cc +++ b/src/detectors/BEMC/BEMC.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,9 @@ extern "C" { void InitPlugin(JApplication* app) { using namespace eicrecon; + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + const auto hit_level = split_timeframes ? JEventLevel::Timeslice : JEventLevel::PhysicsEvent; InitJANAPlugin(app); @@ -250,7 +254,7 @@ void InitPlugin(JApplication* app) { .readout = "EcalBarrelScFiHits", .fields = {"fiber", "z"}, }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( "EcalBarrelScFiRecHits", {"EcalBarrelScFiRawHits"}, {"EcalBarrelScFiRecHits"}, @@ -272,7 +276,7 @@ void InitPlugin(JApplication* app) { .maskPos = "xy", .maskPosFields = {"fiber", "z"}, }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( "EcalBarrelScFiProtoClusters", {"EcalBarrelScFiRecHits"}, {"EcalBarrelScFiProtoClusters"}, @@ -374,7 +378,7 @@ void InitPlugin(JApplication* app) { .readout = "EcalBarrelImagingHits", .timeWindow = EcalBarrelImaging_timeWindow, }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( "EcalBarrelImagingRawHits", {"EventHeader", "EcalBarrelImagingProcessedHits"}, @@ -391,7 +395,7 @@ void InitPlugin(JApplication* app) { .corrMeanScale = "1.0", .readout = "EcalBarrelImagingHits", }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( "EcalBarrelImagingRecHits", {"EcalBarrelImagingRawHits"}, {"EcalBarrelImagingRecHits"}, @@ -408,7 +412,7 @@ void InitPlugin(JApplication* app) { .layerField = "layer", .sectorField = "sector", }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( "EcalBarrelImagingProtoClusters", {"EcalBarrelImagingRecHits"}, diff --git a/src/detectors/BHCAL/BHCAL.cc b/src/detectors/BHCAL/BHCAL.cc index 04ebfdd430..a3e6a94d45 100644 --- a/src/detectors/BHCAL/BHCAL.cc +++ b/src/detectors/BHCAL/BHCAL.cc @@ -2,7 +2,9 @@ // Copyright (C) 2022 - 2024 David Lawrence, Derek Anderson, Wouter Deconinck #include +#include #include +#include #include #include #include @@ -27,6 +29,9 @@ void InitPlugin(JApplication* app) { using namespace eicrecon; InitJANAPlugin(app); + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + const auto hit_level = split_timeframes ? JEventLevel::Timeslice : JEventLevel::PhysicsEvent; // Make sure digi and reco use the same value decltype(CalorimeterHitDigiConfig::capADC) HcalBarrel_capADC = 65536; //65536, 16bit ADC @@ -64,7 +69,7 @@ void InitPlugin(JApplication* app) { .corrMeanScale = "1.0", .readout = "HcalBarrelHits", }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( @@ -82,7 +87,7 @@ void InitPlugin(JApplication* app) { .layerField = "", .sectorField = "", }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); // -------------------------------------------------------------------- diff --git a/src/detectors/BTOF/BTOF.cc b/src/detectors/BTOF/BTOF.cc index 242dc74da0..3808af9c81 100644 --- a/src/detectors/BTOF/BTOF.cc +++ b/src/detectors/BTOF/BTOF.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,26 @@ void InitPlugin(JApplication* app) { InitJANAPlugin(app); using namespace eicrecon; + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + + if (split_timeframes) { + // Produce the time-ordered hit and truth-relation collections consumed by + // TimeframeSplitter. Keep this path separate from the pulse digitization + // used by normal event reconstruction below. + app->Add(new JOmniFactoryGeneratorT( + "TOFBarrelRawHits", {"EventHeader", "TOFBarrelHits"}, + {"TOFBarrelRawHits", "TOFBarrelRawHitLinks", "TOFBarrelRawHitAssociations"}, + { + .threshold = 6.0 * dd4hep::keV, + .timeResolution = 0.025, // [ns] + }, + app, JEventLevel::Timeslice)); + + app->Add(new JOmniFactoryGeneratorT( + "TOFBarrelRecHits", {"TOFBarrelRawHits"}, {"TOFBarrelRecHits"}, {}, app, + JEventLevel::Timeslice)); + } // Convert raw digitized hits into calibrated hits // time walk correction is still TBD @@ -48,8 +69,8 @@ void InitPlugin(JApplication* app) { // Currently it's just a simple weighted average // More sophisticated algorithm TBD app->Add(new JOmniFactoryGeneratorT( - "TOFBarrelClusterHits", {"TOFBarrelSharedRecHits"}, // Input data collection tags - {"TOFBarrelClusterHits"}, // Output data tag + "TOFBarrelClusterHits", {split_timeframes ? "TOFBarrelRecHits" : "TOFBarrelSharedRecHits"}, + {"TOFBarrelClusterHits"}, // Output data tag { .readout = "TOFBarrelHits", .useAve = true, diff --git a/src/detectors/BTRK/BTRK.cc b/src/detectors/BTRK/BTRK.cc index 2b0497daad..0a6c1275e0 100644 --- a/src/detectors/BTRK/BTRK.cc +++ b/src/detectors/BTRK/BTRK.cc @@ -4,7 +4,9 @@ // #include +#include #include +#include #include #include #include @@ -22,6 +24,9 @@ void InitPlugin(JApplication* app) { InitJANAPlugin(app); using namespace eicrecon; + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + const auto hit_level = split_timeframes ? JEventLevel::Timeslice : JEventLevel::PhysicsEvent; // Digitization app->Add(new JOmniFactoryGeneratorT( @@ -30,24 +35,30 @@ void InitPlugin(JApplication* app) { { .threshold = 0.54 * dd4hep::keV, }, - app)); + app, hit_level)); - app->Add(new JOmniFactoryGeneratorT( - "SiBarrelNoiseRawHits", {"EventHeader"}, {"SiBarrelNoiseRawHits"}, - {.addNoise = true, .noise_rate_per_pixel_per_event = 2.0e-7, .readout_name = "SiBarrelHits"}, - app)); + // Per-pixel noise occupancy for the barrel silicon tracker. Configurable via + // SiBarrelNoiseRawHits:noise_rate_per_pixel_per_event (default 2e-7). + if (!split_timeframes) { + app->Add(new JOmniFactoryGeneratorT( + "SiBarrelNoiseRawHits", {"EventHeader"}, {"SiBarrelNoiseRawHits"}, + {.addNoise = true, + .noise_rate_per_pixel_per_event = 2.0e-7, + .readout_name = "SiBarrelHits"}, + app)); - app->Add(new JOmniFactoryGeneratorT>( - "SiBarrelRawHitsWithNoise", // Name of the combiner instance - {"SiBarrelRawHits", "SiBarrelNoiseRawHits"}, // Inputs: original + noise-only - {"SiBarrelRawHitsWithNoise"}, // Output: merged collection - {}, // default config - app)); + app->Add(new JOmniFactoryGeneratorT>( + "SiBarrelRawHitsWithNoise", // Name of the combiner instance + {"SiBarrelRawHits", "SiBarrelNoiseRawHits"}, // Inputs: original + noise-only + {"SiBarrelRawHitsWithNoise"}, // Output: merged collection + {}, // default config + app)); + } // Convert raw digitized hits into hits with geometry info (ready for tracking) app->Add(new JOmniFactoryGeneratorT( - "SiBarrelTrackerRecHits", {"SiBarrelRawHitsWithNoise"}, {"SiBarrelTrackerRecHits"}, - {}, // default config - app)); + "SiBarrelTrackerRecHits", {split_timeframes ? "SiBarrelRawHits" : "SiBarrelRawHitsWithNoise"}, + {"SiBarrelTrackerRecHits"}, {}, // default config + app, hit_level)); } } // extern "C" diff --git a/src/detectors/BVTX/BVTX.cc b/src/detectors/BVTX/BVTX.cc index c15a0210cf..6c03ff2800 100644 --- a/src/detectors/BVTX/BVTX.cc +++ b/src/detectors/BVTX/BVTX.cc @@ -5,7 +5,9 @@ // #include +#include #include +#include #include #include #include @@ -23,6 +25,9 @@ void InitPlugin(JApplication* app) { InitJANAPlugin(app); using namespace eicrecon; + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + const auto hit_level = split_timeframes ? JEventLevel::Timeslice : JEventLevel::PhysicsEvent; // Digitization app->Add(new JOmniFactoryGeneratorT( @@ -31,21 +36,26 @@ void InitPlugin(JApplication* app) { { .threshold = 0.54 * dd4hep::keV, }, - app)); - app->Add(new JOmniFactoryGeneratorT( - "SiBarrelVertexNoiseRawHits", {"EventHeader"}, {"SiBarrelVertexNoiseRawHits"}, - {.addNoise = true, - .noise_rate_per_pixel_per_event = 2.0e-7, - .readout_name = "VertexBarrelHits"}, - app)); - app->Add(new JOmniFactoryGeneratorT>( - "SiBarrelVertexRawHitsWithNoise", {"SiBarrelVertexRawHits", "SiBarrelVertexNoiseRawHits"}, - {"SiBarrelVertexRawHitsWithNoise"}, {}, app)); + app, hit_level)); + + if (!split_timeframes) { + app->Add(new JOmniFactoryGeneratorT( + "SiBarrelVertexNoiseRawHits", {"EventHeader"}, {"SiBarrelVertexNoiseRawHits"}, + {.addNoise = true, + .noise_rate_per_pixel_per_event = 2.0e-7, + .readout_name = "VertexBarrelHits"}, + app)); + + app->Add(new JOmniFactoryGeneratorT>( + "SiBarrelVertexRawHitsWithNoise", {"SiBarrelVertexRawHits", "SiBarrelVertexNoiseRawHits"}, + {"SiBarrelVertexRawHitsWithNoise"}, {}, app)); + } // Convert raw digitized hits into hits with geometry info (ready for tracking) app->Add(new JOmniFactoryGeneratorT( - "SiBarrelVertexRecHits", {"SiBarrelVertexRawHitsWithNoise"}, {"SiBarrelVertexRecHits"}, - {}, // default config - app)); + "SiBarrelVertexRecHits", + {split_timeframes ? "SiBarrelVertexRawHits" : "SiBarrelVertexRawHitsWithNoise"}, + {"SiBarrelVertexRecHits"}, {}, // default config + app, hit_level)); } } // extern "C" diff --git a/src/detectors/ECTOF/ECTOF.cc b/src/detectors/ECTOF/ECTOF.cc index 9bff9a0459..1cf3c6b642 100644 --- a/src/detectors/ECTOF/ECTOF.cc +++ b/src/detectors/ECTOF/ECTOF.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -30,13 +31,25 @@ void InitPlugin(JApplication* app) { InitJANAPlugin(app); using namespace eicrecon; + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + + if (split_timeframes) { + app->Add(new JOmniFactoryGeneratorT( + "TOFEndcapRawHits", {"EventHeader", "TOFEndcapHits"}, + {"TOFEndcapRawHits", "TOFEndcapRawHitLinks", "TOFEndcapRawHitAssociations"}, + {.threshold = 6.0 * dd4hep::keV, .timeResolution = 0.025}, app, JEventLevel::Timeslice)); + app->Add(new JOmniFactoryGeneratorT( + "TOFEndcapRecHits", {"TOFEndcapRawHits"}, {"TOFEndcapRecHits"}, {.timeResolution = 0.025}, + app, JEventLevel::Timeslice)); + } // cluster all hits in a sensor into one hit location // Currently it's just a simple weighted average // More sophisticated algorithm TBD app->Add(new JOmniFactoryGeneratorT( - "TOFEndcapClusterHits", {"TOFEndcapSharedRecHits"}, // Input data collection tags - {"TOFEndcapClusterHits"}, // Output data tag + "TOFEndcapClusterHits", {split_timeframes ? "TOFEndcapRecHits" : "TOFEndcapSharedRecHits"}, + {"TOFEndcapClusterHits"}, // Output data tag { .readout = "TOFEndcapHits", .useAve = true, diff --git a/src/detectors/ECTRK/ECTRK.cc b/src/detectors/ECTRK/ECTRK.cc index b8eda7d508..1c6a394df7 100644 --- a/src/detectors/ECTRK/ECTRK.cc +++ b/src/detectors/ECTRK/ECTRK.cc @@ -4,7 +4,9 @@ // #include +#include #include +#include #include #include #include @@ -22,6 +24,9 @@ void InitPlugin(JApplication* app) { InitJANAPlugin(app); using namespace eicrecon; + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + const auto hit_level = split_timeframes ? JEventLevel::Timeslice : JEventLevel::PhysicsEvent; // Digitization app->Add(new JOmniFactoryGeneratorT( @@ -30,21 +35,27 @@ void InitPlugin(JApplication* app) { { .threshold = 0.54 * dd4hep::keV, }, - app)); - - app->Add(new JOmniFactoryGeneratorT( - "SiEndcapTrackerNoiseRawHits", {"EventHeader"}, {"SiEndcapTrackerNoiseRawHits"}, - {.addNoise = true, - .noise_rate_per_pixel_per_event = 2.0e-7, - .readout_name = "TrackerEndcapHits"}, - app)); - app->Add(new JOmniFactoryGeneratorT>( - "SiEndcapTrackerRawHitsWithNoise", {"SiEndcapTrackerRawHits", "SiEndcapTrackerNoiseRawHits"}, - {"SiEndcapTrackerRawHitsWithNoise"}, {}, app)); + app, hit_level)); + + if (!split_timeframes) { + app->Add(new JOmniFactoryGeneratorT( + "SiEndcapTrackerNoiseRawHits", {"EventHeader"}, {"SiEndcapTrackerNoiseRawHits"}, + {.addNoise = true, + .noise_rate_per_pixel_per_event = 2.0e-7, + .readout_name = "TrackerEndcapHits"}, + app)); + + app->Add(new JOmniFactoryGeneratorT>( + "SiEndcapTrackerRawHitsWithNoise", + {"SiEndcapTrackerRawHits", "SiEndcapTrackerNoiseRawHits"}, + {"SiEndcapTrackerRawHitsWithNoise"}, {}, app)); + } + // Convert raw digitized hits into hits with geometry info (ready for tracking) app->Add(new JOmniFactoryGeneratorT( - "SiEndcapTrackerRecHits", {"SiEndcapTrackerRawHitsWithNoise"}, {"SiEndcapTrackerRecHits"}, - {}, // default config - app)); + "SiEndcapTrackerRecHits", + {split_timeframes ? "SiEndcapTrackerRawHits" : "SiEndcapTrackerRawHitsWithNoise"}, + {"SiEndcapTrackerRecHits"}, {}, // default config + app, hit_level)); } } // extern "C" diff --git a/src/detectors/EEMC/EEMC.cc b/src/detectors/EEMC/EEMC.cc index c432bce26c..fb276fb7af 100644 --- a/src/detectors/EEMC/EEMC.cc +++ b/src/detectors/EEMC/EEMC.cc @@ -2,7 +2,9 @@ // Copyright (C) 2022 - 2025 Sylvester Joosten, Chao, Chao Peng, Whitney Armstrong, Thomas Britton, David Lawrence, Dhevan Gangadharan, Wouter Deconinck, Dmitry Kalinkin, Derek Anderson #include +#include #include +#include #include #include #include @@ -28,6 +30,9 @@ void InitPlugin(JApplication* app) { using namespace eicrecon; InitJANAPlugin(app); + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + const auto hit_level = split_timeframes ? JEventLevel::Timeslice : JEventLevel::PhysicsEvent; // Make sure digi and reco use the same value decltype(CalorimeterHitDigiConfig::capADC) EcalEndcapN_capADC = 16384; //65536, 16bit ADC @@ -59,7 +64,7 @@ void InitPlugin(JApplication* app) { .corrMeanScale = "1.0", .readout = "EcalEndcapNHits", }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( "EcalEndcapNRecHits", {"EcalEndcapNRawHits"}, {"EcalEndcapNRecHits"}, @@ -74,7 +79,7 @@ void InitPlugin(JApplication* app) { .sampFrac = "0.96", .readout = "EcalEndcapNHits", }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( "EcalEndcapNTruthProtoClusters", {"EcalEndcapNRecHits", "EcalEndcapNHits"}, @@ -144,56 +149,62 @@ void InitPlugin(JApplication* app) { )); app->Add(new JOmniFactoryGeneratorT( - "EcalEndcapNClustersWithoutPID", + split_timeframes ? "EcalEndcapNClusters" : "EcalEndcapNClustersWithoutPID", {"EcalEndcapNClustersWithoutPIDAndShapes", "EcalEndcapNClusterAssociationsWithoutPIDAndShapes"}, - {"EcalEndcapNClustersWithoutPID", "EcalEndcapNClusterLinksWithoutPID", - "EcalEndcapNClusterAssociationsWithoutPID"}, + split_timeframes ? std::vector{"EcalEndcapNClusters", "EcalEndcapNClusterLinks", + "EcalEndcapNClusterAssociations"} + : std::vector{"EcalEndcapNClustersWithoutPID", + "EcalEndcapNClusterLinksWithoutPID", + "EcalEndcapNClusterAssociationsWithoutPID"}, {.energyWeight = "log", .logWeightBase = 3.6}, app)); - app->Add(new JOmniFactoryGeneratorT( - "EcalEndcapNParticleIDPreML", - { - "EcalEndcapNClustersWithoutPID", - "EcalEndcapNClusterAssociationsWithoutPID", - }, - { - "EcalEndcapNParticleIDInput_features", - "EcalEndcapNParticleIDTarget", - }, - app)); - app->Add(new JOmniFactoryGeneratorT( - "EcalEndcapNParticleIDInference", - { - "EcalEndcapNParticleIDInput_features", - }, - { - "EcalEndcapNParticleIDOutput_label", - "EcalEndcapNParticleIDOutput_probability_tensor", - }, - { - .modelPath = "calibrations/onnx/EcalEndcapN_pi_rejection.onnx", - }, - app)); - app->Add(new JOmniFactoryGeneratorT( - "EcalEndcapNParticleIDPostML", - { - "EcalEndcapNClustersWithoutPID", - "EcalEndcapNClusterAssociationsWithoutPID", - "EcalEndcapNParticleIDOutput_probability_tensor", - }, + if (!split_timeframes) { + app->Add(new JOmniFactoryGeneratorT( + "EcalEndcapNParticleIDPreML", + { + "EcalEndcapNClustersWithoutPID", + "EcalEndcapNClusterAssociationsWithoutPID", + }, + { + "EcalEndcapNParticleIDInput_features", + "EcalEndcapNParticleIDTarget", + }, + app)); + app->Add(new JOmniFactoryGeneratorT( + "EcalEndcapNParticleIDInference", + { + "EcalEndcapNParticleIDInput_features", + }, + { + "EcalEndcapNParticleIDOutput_label", + "EcalEndcapNParticleIDOutput_probability_tensor", + }, + { + .modelPath = "calibrations/onnx/EcalEndcapN_pi_rejection.onnx", + }, + app)); + app->Add(new JOmniFactoryGeneratorT( + "EcalEndcapNParticleIDPostML", + { + "EcalEndcapNClustersWithoutPID", + "EcalEndcapNClusterAssociationsWithoutPID", + "EcalEndcapNParticleIDOutput_probability_tensor", + }, - { - "EcalEndcapNClusters", - "EcalEndcapNClusterLinks", - "EcalEndcapNClusterAssociations", - "EcalEndcapNClusterParticleIDs", - }, - app)); + { + "EcalEndcapNClusters", + "EcalEndcapNClusterLinks", + "EcalEndcapNClusterAssociations", + "EcalEndcapNClusterParticleIDs", + }, + app)); + } app->Add(new JOmniFactoryGeneratorT( "EcalEndcapNSplitMergeProtoClusters", - {"EcalEndcapNTrackClusterMatches", "EcalEndcapNClustersWithoutPID", + {"EcalEndcapNTrackClusterMatches", + split_timeframes ? "EcalEndcapNClusters" : "EcalEndcapNClustersWithoutPID", "CalorimeterTrackProjections"}, {"EcalEndcapNSplitMergeProtoClusters", "EcalEndcapNTrackSplitMergeProtoClusterLinks"}, {.minSigCut = -1.0, diff --git a/src/detectors/EHCAL/EHCAL.cc b/src/detectors/EHCAL/EHCAL.cc index 03d69ceaf3..04410df7b9 100644 --- a/src/detectors/EHCAL/EHCAL.cc +++ b/src/detectors/EHCAL/EHCAL.cc @@ -2,7 +2,9 @@ // Copyright (C) 2022 - 2025 Sylvester Joosten, Chao, Chao Peng, Whitney Armstrong, David Lawrence, Friederike Bock, Nathan Brei, Wouter Deconinck, Dmitry Kalinkin, Derek Anderson #include +#include #include +#include #include #include #include @@ -25,6 +27,9 @@ void InitPlugin(JApplication* app) { using namespace eicrecon; InitJANAPlugin(app); + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + const auto hit_level = split_timeframes ? JEventLevel::Timeslice : JEventLevel::PhysicsEvent; // Make sure digi and reco use the same value decltype(CalorimeterHitDigiConfig::capADC) HcalEndcapN_capADC = 32768; // assuming 15 bit ADC like FHCal @@ -50,7 +55,7 @@ void InitPlugin(JApplication* app) { .corrMeanScale = "1.0", .readout = "HcalEndcapNHits", }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( "HcalEndcapNRecHits", {"HcalEndcapNRawHits"}, {"HcalEndcapNRecHits"}, @@ -67,7 +72,7 @@ void InitPlugin(JApplication* app) { "0.0095", // from latest study - implement at level of reco hits rather than clusters .readout = "HcalEndcapNHits", }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( "HcalEndcapNMergedHits", {"HcalEndcapNRecHits"}, {"HcalEndcapNMergedHits"}, diff --git a/src/detectors/FEMC/FEMC.cc b/src/detectors/FEMC/FEMC.cc index b101023ac1..4ad0b6d27c 100644 --- a/src/detectors/FEMC/FEMC.cc +++ b/src/detectors/FEMC/FEMC.cc @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,9 @@ void InitPlugin(JApplication* app) { using namespace eicrecon; InitJANAPlugin(app); + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + const auto hit_level = split_timeframes ? JEventLevel::Timeslice : JEventLevel::PhysicsEvent; auto log_service = app->GetService(); auto mLog = log_service->logger("FEMC"); @@ -89,7 +93,7 @@ void InitPlugin(JApplication* app) { .corrMeanScale = "1.0", .readout = "EcalEndcapPHits", }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); } else if (EcalEndcapP_homogeneousFlag == 2) { app->Add(new JOmniFactoryGeneratorT( @@ -115,7 +119,7 @@ void InitPlugin(JApplication* app) { .readout = "EcalEndcapPHits", .fields = {"fiber_x", "fiber_y"}, }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); } @@ -134,7 +138,7 @@ void InitPlugin(JApplication* app) { .sampFrac = "1.00", // already taken care in DIGI code above .readout = "EcalEndcapPHits", }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( "EcalEndcapPTruthProtoClusters", {"EcalEndcapPRecHits", "EcalEndcapPHits"}, diff --git a/src/detectors/FHCAL/FHCAL.cc b/src/detectors/FHCAL/FHCAL.cc index 3702f6ee3c..20cb17d10e 100644 --- a/src/detectors/FHCAL/FHCAL.cc +++ b/src/detectors/FHCAL/FHCAL.cc @@ -2,7 +2,9 @@ // Copyright (C) 2023 - 2025 Friederike Bock, Wouter Deconinck #include +#include #include +#include #include #include #include @@ -29,6 +31,9 @@ void InitPlugin(JApplication* app) { using namespace eicrecon; InitJANAPlugin(app); + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + const auto hit_level = split_timeframes ? JEventLevel::Timeslice : JEventLevel::PhysicsEvent; // Make sure digi and reco use the same value decltype(CalorimeterHitDigiConfig::capADC) HcalEndcapPInsert_capADC = 32768; @@ -53,7 +58,7 @@ void InitPlugin(JApplication* app) { .corrMeanScale = "1.0", .readout = "HcalEndcapPInsertHits", }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( "HcalEndcapPInsertRecHits", {"HcalEndcapPInsertRawHits"}, {"HcalEndcapPInsertRecHits"}, @@ -70,7 +75,7 @@ void InitPlugin(JApplication* app) { .readout = "HcalEndcapPInsertHits", .layerField = "layer", }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( "HcalEndcapPInsertMergedHits", {"HcalEndcapPInsertRecHits"}, {"HcalEndcapPInsertMergedHits"}, @@ -194,7 +199,7 @@ void InitPlugin(JApplication* app) { .readout = "LFHCALHits", .fields = {"layerz"}, }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( "LFHCALRecHits", {"LFHCALRawHits"}, {"LFHCALRecHits"}, @@ -210,7 +215,7 @@ void InitPlugin(JApplication* app) { .readout = "LFHCALHits", .layerField = "rlayerz", }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( "LFHCALTruthProtoClusters", {"LFHCALRecHits", "LFHCALHits"}, {"LFHCALTruthProtoClusters"}, diff --git a/src/detectors/FOFFMTRK/FOFFMTRK.cc b/src/detectors/FOFFMTRK/FOFFMTRK.cc index 5ff1a6d710..066b7117b9 100644 --- a/src/detectors/FOFFMTRK/FOFFMTRK.cc +++ b/src/detectors/FOFFMTRK/FOFFMTRK.cc @@ -4,7 +4,9 @@ // #include +#include #include +#include #include #include #include @@ -19,6 +21,9 @@ extern "C" { void InitPlugin(JApplication* app) { InitJANAPlugin(app); using namespace eicrecon; + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + const auto hit_level = split_timeframes ? JEventLevel::Timeslice : JEventLevel::PhysicsEvent; //Digitized hits, especially for thresholds app->Add(new JOmniFactoryGeneratorT( @@ -29,14 +34,14 @@ void InitPlugin(JApplication* app) { .threshold = 10.0 * dd4hep::keV, .timeResolution = 8, }, - app)); + app, hit_level)); app->Add(new JOmniFactoryGeneratorT( "ForwardOffMTrackerRecHits", {"ForwardOffMTrackerRawHits"}, {"ForwardOffMTrackerRecHits"}, { .timeResolution = 8, }, - app)); + app, hit_level)); app->Add(new JOmniFactoryGeneratorT( "ForwardOffMRecParticles", {"MCParticles", "ForwardOffMTrackerRecHits"}, diff --git a/src/detectors/LOWQ2/LOWQ2.cc b/src/detectors/LOWQ2/LOWQ2.cc index 4ef8cffa8f..5bacccde72 100644 --- a/src/detectors/LOWQ2/LOWQ2.cc +++ b/src/detectors/LOWQ2/LOWQ2.cc @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +46,9 @@ void InitPlugin(JApplication* app) { InitJANAPlugin(app); using namespace eicrecon; + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + const auto hit_level = split_timeframes ? JEventLevel::Timeslice : JEventLevel::PhysicsEvent; std::string readout = "TaggerTrackerHits"; @@ -96,7 +100,7 @@ void InitPlugin(JApplication* app) { .threshold = 1.5 * edm4eic::unit::keV, .timeResolution = 2 * edm4eic::unit::ns, }, - app)); + app, hit_level)); // Convert raw digitized hits into hits with geometry info (ready for tracking) app->Add(new JOmniFactoryGeneratorT( @@ -104,7 +108,7 @@ void InitPlugin(JApplication* app) { { .timeResolution = 2, }, - app)); + app, hit_level)); // Divide collection based on geometry segmentation labels // This should really be done before digitization as summing hits in the same cell couldn't even be mixed between layers. At the moment just prep for clustering. diff --git a/src/detectors/LUMISPECCAL/LUMISPECCAL.cc b/src/detectors/LUMISPECCAL/LUMISPECCAL.cc index 8fd5a33eac..2c9e8b145b 100644 --- a/src/detectors/LUMISPECCAL/LUMISPECCAL.cc +++ b/src/detectors/LUMISPECCAL/LUMISPECCAL.cc @@ -2,7 +2,9 @@ // Copyright (C) 2022 - 2025 Sylvester Joosten, Chao, Chao Peng, Whitney Armstrong, David Lawrence, Dhevan Gangadharan, Nathan Brei,, Wouter Deconinck, Dmitry Kalinkin, Derek Anderson #include +#include #include +#include #include #include #include @@ -23,6 +25,9 @@ void InitPlugin(JApplication* app) { using namespace eicrecon; InitJANAPlugin(app); + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + const auto hit_level = split_timeframes ? JEventLevel::Timeslice : JEventLevel::PhysicsEvent; app->Add(new JOmniFactoryGeneratorT( "EcalLumiSpecRawHits", {"EventHeader", "EcalLumiSpecHits"}, @@ -38,7 +43,7 @@ void InitPlugin(JApplication* app) { .corrMeanScale = "1.0", .readout = "EcalLumiSpecHits", }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( "EcalLumiSpecRecHits", {"EcalLumiSpecRawHits"}, {"EcalLumiSpecRecHits"}, @@ -53,7 +58,7 @@ void InitPlugin(JApplication* app) { .sampFrac = "1.0", .readout = "EcalLumiSpecHits", }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( "EcalLumiSpecTruthProtoClusters", {"EcalLumiSpecRecHits", "EcalLumiSpecHits"}, diff --git a/src/detectors/MPGD/MPGD.cc b/src/detectors/MPGD/MPGD.cc index 4c2879dcca..3c4a6e2c97 100644 --- a/src/detectors/MPGD/MPGD.cc +++ b/src/detectors/MPGD/MPGD.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,9 @@ void InitPlugin(JApplication* app) { InitJANAPlugin(app); using namespace eicrecon; + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + const auto hit_level = split_timeframes ? JEventLevel::Timeslice : JEventLevel::PhysicsEvent; // ***** PIXEL or 2DSTRIP DIGITIZATION? // - This determines which of the MPGDTrackerDigi or SiliconTrackerDigi @@ -106,7 +110,7 @@ void InitPlugin(JApplication* app) { .threshold = 100 * dd4hep::eV, .timeResolution = 10, }, - app)); + app, hit_level)); } else { // Configuration parameters MPGDTrackerDigiConfig digi_cfg; @@ -131,7 +135,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "MPGDBarrelRawHits", {"EventHeader", "MPGDBarrelHits"}, {"MPGDBarrelRawHits", "MPGDBarrelRawHitLinks", "MPGDBarrelRawHitAssociations"}, digi_cfg, - app)); + app, hit_level)); } // Convert raw digitized hits into hits with geometry info (ready for tracking) @@ -142,7 +146,7 @@ void InitPlugin(JApplication* app) { { .timeResolution = 10, }, - app)); + app, hit_level)); } else { MPGDHitReconstructionConfig reco_cfg; reco_cfg.readout = "MPGDBarrelHits"; @@ -151,7 +155,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "MPGDBarrelRecHits", {"MPGDBarrelRawHits"}, // Input data collection tags {"MPGDBarrelRecHits"}, // Output data tag - reco_cfg, app)); + reco_cfg, app, hit_level)); } // ***** OuterMPGDBarrel @@ -165,7 +169,7 @@ void InitPlugin(JApplication* app) { .threshold = 100 * dd4hep::eV, .timeResolution = 10, }, - app)); + app, hit_level)); } else { MPGDTrackerDigiConfig digi_cfg; digi_cfg.readout = "OuterMPGDBarrelHits"; @@ -186,7 +190,7 @@ void InitPlugin(JApplication* app) { "OuterMPGDBarrelRawHits", {"EventHeader", "OuterMPGDBarrelHits"}, {"OuterMPGDBarrelRawHits", "OuterMPGDBarrelRawHitLinks", "OuterMPGDBarrelRawHitAssociations"}, - digi_cfg, app)); + digi_cfg, app, hit_level)); } // Convert raw digitized hits into hits with geometry info (ready for tracking) @@ -197,7 +201,7 @@ void InitPlugin(JApplication* app) { { .timeResolution = 10, }, - app)); + app, hit_level)); } else { MPGDHitReconstructionConfig reco_cfg; reco_cfg.readout = "OuterMPGDBarrelHits"; @@ -206,7 +210,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "OuterMPGDBarrelRecHits", {"OuterMPGDBarrelRawHits"}, // Input data collection tags {"OuterMPGDBarrelRecHits"}, // Output data tag - reco_cfg, app)); + reco_cfg, app, hit_level)); } // ***** "BackwardMPGDEndcap" @@ -219,7 +223,7 @@ void InitPlugin(JApplication* app) { .threshold = 100 * dd4hep::eV, .timeResolution = 10, }, - app)); + app, hit_level)); // Convert raw digitized hits into hits with geometry info (ready for tracking) app->Add(new JOmniFactoryGeneratorT( @@ -228,7 +232,7 @@ void InitPlugin(JApplication* app) { { .timeResolution = 10, }, - app)); + app, hit_level)); // ""ForwardMPGDEndcap" // Digitization @@ -240,7 +244,7 @@ void InitPlugin(JApplication* app) { .threshold = 100 * dd4hep::eV, .timeResolution = 10, }, - app)); + app, hit_level)); // Convert raw digitized hits into hits with geometry info (ready for tracking) app->Add(new JOmniFactoryGeneratorT( @@ -249,6 +253,6 @@ void InitPlugin(JApplication* app) { { .timeResolution = 10, }, - app)); + app, hit_level)); } } // extern "C" diff --git a/src/detectors/RPOTS/RPOTS.cc b/src/detectors/RPOTS/RPOTS.cc index c28d68e920..3b660b4566 100644 --- a/src/detectors/RPOTS/RPOTS.cc +++ b/src/detectors/RPOTS/RPOTS.cc @@ -4,7 +4,9 @@ // #include +#include #include +#include #include #include #include @@ -21,6 +23,9 @@ extern "C" { void InitPlugin(JApplication* app) { InitJANAPlugin(app); using namespace eicrecon; + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + const auto hit_level = split_timeframes ? JEventLevel::Timeslice : JEventLevel::PhysicsEvent; MatrixTransferStaticConfig recon_cfg; PolynomialMatrixReconstructionConfig recon_poly_cfg; @@ -33,14 +38,14 @@ void InitPlugin(JApplication* app) { .threshold = 10.0 * dd4hep::keV, .timeResolution = 8, }, - app)); + app, hit_level)); app->Add(new JOmniFactoryGeneratorT( "ForwardRomanPotRecHits", {"ForwardRomanPotRawHits"}, {"ForwardRomanPotRecHits"}, { .timeResolution = 8, }, - app)); + app, hit_level)); app->Add(new JOmniFactoryGeneratorT( "ForwardRomanPotStaticRecParticles", diff --git a/src/detectors/ZDC/ZDC.cc b/src/detectors/ZDC/ZDC.cc index cc4c528ffb..c2f0d8a768 100644 --- a/src/detectors/ZDC/ZDC.cc +++ b/src/detectors/ZDC/ZDC.cc @@ -2,7 +2,9 @@ // Copyright (C) 2021 - 2025, Chao Peng, Sylvester Joosten, Whitney Armstrong, David Lawrence, Friederike Bock, Wouter Deconinck, Nathan Brei, Sebouh Paul, Dmitry Kalinkin, Barak Schmookler #include +#include #include +#include #include #include #include @@ -26,6 +28,9 @@ void InitPlugin(JApplication* app) { using namespace eicrecon; InitJANAPlugin(app); + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + const auto hit_level = split_timeframes ? JEventLevel::Timeslice : JEventLevel::PhysicsEvent; // LYSO part of the ZDC app->Add(new JOmniFactoryGeneratorT( @@ -43,7 +48,7 @@ void InitPlugin(JApplication* app) { .corrMeanScale = "1.0", .readout = "EcalFarForwardZDCHits", }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( "EcalFarForwardZDCRecHits", {"EcalFarForwardZDCRawHits"}, {"EcalFarForwardZDCRecHits"}, @@ -58,7 +63,7 @@ void InitPlugin(JApplication* app) { .sampFrac = "1.0", .readout = "EcalFarForwardZDCHits", }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( "EcalFarForwardZDCTruthProtoClusters", {"EcalFarForwardZDCRecHits", "EcalFarForwardZDCHits"}, @@ -152,7 +157,7 @@ void InitPlugin(JApplication* app) { .corrMeanScale = "1.0", .readout = "HcalFarForwardZDCHits", }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( @@ -170,7 +175,7 @@ void InitPlugin(JApplication* app) { .layerField = "layer", .sectorField = "system", }, - app // TODO: Remove me once fixed + app, hit_level // TODO: Remove me once fixed )); app->Add(new JOmniFactoryGeneratorT( diff --git a/src/extensions/jana/JOmniFactoryGeneratorT.h b/src/extensions/jana/JOmniFactoryGeneratorT.h index f5846bf4fc..1326b3b51e 100644 --- a/src/extensions/jana/JOmniFactoryGeneratorT.h +++ b/src/extensions/jana/JOmniFactoryGeneratorT.h @@ -18,6 +18,7 @@ template class JOmniFactoryGeneratorT : public JFactoryGenerato std::vector m_default_input_tags; std::vector m_default_output_tags; FactoryConfigType m_default_cfg; /// Must be properly copyable! + JEventLevel m_level = JEventLevel::PhysicsEvent; }; struct UntypedWiring { @@ -30,21 +31,25 @@ template class JOmniFactoryGeneratorT : public JFactoryGenerato public: explicit JOmniFactoryGeneratorT(std::string tag, std::vector default_input_tags, std::vector default_output_tags, - FactoryConfigType cfg, JApplication* app) { + FactoryConfigType cfg, JApplication* app, + JEventLevel level = JEventLevel::PhysicsEvent) { m_app = app; m_wirings.push_back({.m_tag = tag, .m_default_input_tags = default_input_tags, .m_default_output_tags = default_output_tags, - .m_default_cfg = cfg}); + .m_default_cfg = cfg, + .m_level = level}); }; explicit JOmniFactoryGeneratorT(std::string tag, std::vector default_input_tags, - std::vector default_output_tags, JApplication* app) { + std::vector default_output_tags, JApplication* app, + JEventLevel level = JEventLevel::PhysicsEvent) { m_app = app; m_wirings.push_back({.m_tag = tag, .m_default_input_tags = default_input_tags, .m_default_output_tags = default_output_tags, - .m_default_cfg = {}}); + .m_default_cfg = {}, + .m_level = level}); } explicit JOmniFactoryGeneratorT(JApplication* app) : m_app(app) {} @@ -55,7 +60,8 @@ template class JOmniFactoryGeneratorT : public JFactoryGenerato m_wirings.push_back({.m_tag = tag, .m_default_input_tags = default_input_tags, .m_default_output_tags = default_output_tags, - .m_default_cfg = cfg}); + .m_default_cfg = cfg, + .m_level = JEventLevel::PhysicsEvent}); } void AddWiring(std::string tag, std::vector default_input_tags, @@ -70,7 +76,8 @@ template class JOmniFactoryGeneratorT : public JFactoryGenerato m_wirings.push_back({.m_tag = tag, .m_default_input_tags = default_input_tags, .m_default_output_tags = default_output_tags, - .m_default_cfg = config}); + .m_default_cfg = config, + .m_level = JEventLevel::PhysicsEvent}); } void GenerateFactories(JFactorySet* factory_set) override { @@ -82,6 +89,7 @@ template class JOmniFactoryGeneratorT : public JFactoryGenerato factory->SetPluginName(this->GetPluginName()); factory->SetFactoryName(JTypeInfo::demangle()); factory->config() = wiring.m_default_cfg; + factory->SetLevel(wiring.m_level); // Set up all of the wiring prereqs so that Init() can do its thing // Specifically, it needs valid input/output tags, a valid logger, and diff --git a/src/factories/CMakeLists.txt b/src/factories/CMakeLists.txt index e040c9fc1b..735817816e 100644 --- a/src/factories/CMakeLists.txt +++ b/src/factories/CMakeLists.txt @@ -7,3 +7,4 @@ add_subdirectory(pid) add_subdirectory(pid_lut) add_subdirectory(reco) add_subdirectory(tracking) +add_subdirectory(event_building) diff --git a/src/factories/event_building/CMakeLists.txt b/src/factories/event_building/CMakeLists.txt new file mode 100644 index 0000000000..c19f19b48a --- /dev/null +++ b/src/factories/event_building/CMakeLists.txt @@ -0,0 +1,2 @@ +set(PLUGIN_NAME "factories_event_building") +plugin_headers_only(${PLUGIN_NAME}) diff --git a/src/factories/event_building/HitTimeAlignment_factory.h b/src/factories/event_building/HitTimeAlignment_factory.h new file mode 100644 index 0000000000..17b55e0f8c --- /dev/null +++ b/src/factories/event_building/HitTimeAlignment_factory.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright (C) 2026 Takuya Kumaoka + +#pragma once + +#include + +#include "algorithms/event_building/HitTimeAlignment.h" +#include "extensions/jana/JOmniFactory.h" + +namespace eicrecon { + +template +class HitTimeAlignment_factory + : public JOmniFactory, HitTimeAlignmentConfig> { +public: + using FactoryT = JOmniFactory, HitTimeAlignmentConfig>; + using AlgoT = HitTimeAlignment; + +private: + std::unique_ptr m_algo; + + typename FactoryT::template PodioInput m_hits_in{this}; + typename FactoryT::template PodioOutput m_hits_out{this}; + + typename FactoryT::template ParameterRef m_refInverseVelocity{ + this, "referenceInverseVelocity", this->config().refInverseVelocity}; + +public: + void Configure() { + m_algo = std::make_unique(this->GetPrefix()); + m_algo->level(static_cast(this->logger()->level())); + m_algo->applyConfig(this->config()); + m_algo->init(); + } + + void Process(int32_t /* run_number */, uint64_t /* event_number */) { + const auto* hits_in = m_hits_in(); + + if (hits_in != nullptr) { + m_algo->process({hits_in}, {m_hits_out().get()}); + } + } +}; + +} // namespace eicrecon diff --git a/src/global/CMakeLists.txt b/src/global/CMakeLists.txt index bdca9fd35e..81dc003eca 100644 --- a/src/global/CMakeLists.txt +++ b/src/global/CMakeLists.txt @@ -4,3 +4,6 @@ add_subdirectory(particle_flow) add_subdirectory(pid) add_subdirectory(pid_lut) add_subdirectory(beam) +if(JANA_VERSION VERSION_GREATER_EQUAL "2026.01.00") + add_subdirectory(splitting) +endif() diff --git a/src/global/splitting/CMakeLists.txt b/src/global/splitting/CMakeLists.txt new file mode 100644 index 0000000000..74613b3963 --- /dev/null +++ b/src/global/splitting/CMakeLists.txt @@ -0,0 +1,32 @@ +get_filename_component(PLUGIN_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) + +# Function creates ${PLUGIN_NAME}_plugin and ${PLUGIN_NAME}_library targets +# Setting default includes, libraries and installation paths +plugin_add(${PLUGIN_NAME} PLUGIN_USE_CC_ONLY) + +# The macro grabs sources as *.cc *.cpp *.c and headers as *.h *.hh *.hpp Then +# correctly sets sources for ${_name}_plugin and ${_name}_library targets Adds +# headers to the correct installation directory +plugin_glob_all(${PLUGIN_NAME}) + +# Find dependencies +plugin_add_cern_root(${PLUGIN_NAME}) +plugin_add_event_model(${PLUGIN_NAME}) + +# Add include directories (works same as target_include_directories) +# plugin_include_directories(${PLUGIN_NAME} SYSTEM PUBLIC ... ) + +# Add libraries (same as target_include_directories but for both plugin and +# library) +plugin_link_libraries(${PLUGIN_NAME} algorithms_digi_library + algorithms_tracking_library) +# +# Add include directories (works same as target_include_directories) +# plugin_include_directories(${PLUGIN_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR} +# SYSTEM PUBLIC ${podio_INCLUDE_DIR} ${EDM4HEP_INCLUDE_DIR} +# ${DD4hep_INCLUDE_DIRS} ${ROOT_INCLUDE_DIRS} ${EDM4EIC_INCLUDE_DIRS}) +# +# Add libraries (works same as target_include_directories) +# plugin_link_libraries(${PLUGIN_NAME} ${ROOT_LIBRARIES} +# algorithms_tracking_library EDM4HEP::edm4hep EDM4EIC::edm4eic spdlog::spdlog +# Boost::boost ${ROOT_EG_LIBRARY}) diff --git a/src/global/splitting/TimeframeSplitter.cc b/src/global/splitting/TimeframeSplitter.cc new file mode 100644 index 0000000000..725a7422de --- /dev/null +++ b/src/global/splitting/TimeframeSplitter.cc @@ -0,0 +1,659 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright (C) 2026 Takuya Kumaoka + +#include "TimeframeSplitter.h" + +#include +#include +#include +#include +#include +#include + +TimeframeSplitter::TimeframeSplitter() { + SetTypeName(NAME_OF_THIS); + SetParentLevel(JEventLevel::Timeslice); + SetChildLevel(JEventLevel::PhysicsEvent); +} + +std::uint64_t TimeframeSplitter::objIdKey(const podio::ObjectID& objectID) { + const auto collectionID = + static_cast(static_cast(objectID.collectionID)); + const auto index = static_cast(static_cast(objectID.index)); + + return (collectionID << 32U) | index; +} + +TimeframeSplitter::TrackerAssociationIndex TimeframeSplitter::buildTrkAssoId( + const edm4eic::MCRecoTrackerHitAssociationCollection* associations) { + + TrackerAssociationIndex index; + if (associations == nullptr) + return index; + index.reserve(associations->size()); + + for (size_t assoId = 0; assoId < associations->size(); ++assoId) { + const auto association = associations->at(assoId); + const auto rawHit = association.getRawHit(); + if (!rawHit.isAvailable()) + continue; + index[objIdKey(rawHit.getObjectID())].push_back(assoId); + } + + return index; +} + +TimeframeSplitter::CalorimeterAssociationIndex TimeframeSplitter::buildCalAssoId( + const edm4eic::MCRecoCalorimeterHitAssociationCollection* associations) { + + CalorimeterAssociationIndex index; + if (associations == nullptr) + return index; + index.reserve(associations->size()); + + for (size_t assoId = 0; assoId < associations->size(); ++assoId) { + const auto association = associations->at(assoId); + const auto rawHit = association.getRawHit(); + if (!rawHit.isAvailable()) + continue; + index[objIdKey(rawHit.getObjectID())].push_back(assoId); + } + + return index; +} + +bool TimeframeSplitter::overlapsTimeWindow(double hitTime, double resolution, double window_start, + double window_end) { + return hitTime + resolution > window_start && hitTime - resolution < window_end; +} + +bool TimeframeSplitter::judgeOverTimeWindow(double hitTime, double resolution, double window_end) { + return hitTime - resolution >= window_end; +} + +bool TimeframeSplitter::isValidEtaPhiBin(int etaBin, int phiBin) { + return 0 <= etaBin && etaBin < kEtaPhiBins && 0 <= phiBin && phiBin < kEtaPhiBins; +} + +bool TimeframeSplitter::judgeHitInTimeSlice(double hitTime, double timeResolution, + double timeslice_start, double timeslice_end) { + return !(hitTime + timeResolution < timeslice_start || hitTime - timeResolution > timeslice_end); +} + +std::pair TimeframeSplitter::etaPhiBins(double hitEta, double hitPhi, double etaMin, + double etaMax, int bShift) { + const double etaBinWidth = (etaMax - etaMin) / kEtaPhiBins; + const double phiMin = -std::numbers::pi; + const double phiMax = std::numbers::pi; + const double phiBinWidth = (phiMax - phiMin) / kEtaPhiBins; + + const double halfEtaBin = 0.5 * etaBinWidth * bShift; + const double halfPhiBin = 0.5 * phiBinWidth * bShift; + const double shiftedEtaMin = etaMin + halfEtaBin; + const double shiftedEtaMax = etaMax + halfEtaBin; + const double shiftedPhiMin = phiMin + halfPhiBin; + const double shiftedPhiMax = phiMax + halfPhiBin; + + if (hitEta < shiftedEtaMin || hitEta >= shiftedEtaMax || hitPhi < shiftedPhiMin || + hitPhi >= shiftedPhiMax) { + return {kInvalidEtaPhiBin, kInvalidEtaPhiBin}; + } + + const int etaBin = static_cast(std::floor((hitEta - shiftedEtaMin) / etaBinWidth)); + const int phiBin = static_cast(std::floor((hitPhi - shiftedPhiMin) / phiBinWidth)); + return {etaBin, phiBin}; +} + +size_t TimeframeSplitter::countGridCellsWithMultiplicity(const EtaPhiGrid& grid0, + const EtaPhiGrid& gridShifted, + const EtaPhiTimeGrid& gridTime0, + const EtaPhiTimeGrid& gridShiftedTime, + int threshold, double& averageTime) { + size_t count = 0; + double timeSum = 0.0; + for (size_t iEta = 0; iEta < kEtaPhiBins; ++iEta) { + for (size_t iPhi = 0; iPhi < kEtaPhiBins; ++iPhi) { + if (grid0[iEta][iPhi] >= threshold || gridShifted[iEta][iPhi] >= threshold) { + count++; + if (grid0[iEta][iPhi] >= threshold) { + timeSum += gridTime0[iEta][iPhi] / grid0[iEta][iPhi]; + } else if (gridShifted[iEta][iPhi] >= threshold) { + timeSum += gridShiftedTime[iEta][iPhi] / gridShifted[iEta][iPhi]; + } + } + } + } + averageTime = count > 0 ? timeSum / count : 0.0; + return count; +} + +double TimeframeSplitter::averageSelectedTriggerTime(const std::array& values, + const std::array& times, + std::initializer_list indices, + double fallbackTime) { + size_t count = 0; + double timeSum = 0.0; + for (const size_t index : indices) { + if (values[index] <= 0.0) + continue; + timeSum += times[index]; + count++; + } + return count > 0 ? timeSum / count : fallbackTime; +} + +double TimeframeSplitter::trkTimeResolution(size_t detectorID) { + if (detectorID < 3) + return timeResolution_ACLGad(); + if (detectorID < 7) + return timeResolution_MPGD(); + return timeResolution_SiMaps(); +} + +std::pair TimeframeSplitter::backEndEtaPhiBins(double hitEta, double hitPhi, int bShift) { + // MPGD backward Endcap range -3.6 < eta < -1.72, +5%: -3.78 < eta < -1.634 + const double etaMin = -3.78; + const double etaMax = -1.634; + return etaPhiBins(hitEta, hitPhi, etaMin, etaMax, bShift); +} + +std::pair TimeframeSplitter::barrelEtaPhiBins(double hitEta, double hitPhi, int bShift) { + // MPGD barrel In range -1.49 < eta < 1.722, +5%: -1.56 < eta < 1.81 + // MPGD barrel Out range -1.56 < eta < 1.61, +5%: -1.64 < eta < 1.70 + // TOF barrel range -1.39 < eta < 1.39, +5%: -1.46 < eta < 1.46 + // ECal barrel range -1.71 < eta < 1.31, +5%: -1.80 < eta < 1.38 + const double etaMin = -1.80; + const double etaMax = 1.81; + return etaPhiBins(hitEta, hitPhi, etaMin, etaMax, bShift); +} + +std::pair TimeframeSplitter::forwardEndEtaPhiBins(double hitEta, double hitPhi, + int bShift) { + // MPGD forward Endcap range 2.0 < eta < 3.35, +5%: 1.90 < eta < 3.52 + // TOF forward Endcap range 1.86 < eta < 3.85, +5%: 1.77 < eta < 4.04 + // ECal forward Endcap range 1.4 < eta < 3.5, +5%: 1.33 < eta < 3.68 + const double etaMin = 1.77; + const double etaMax = 4.04; + return etaPhiBins(hitEta, hitPhi, etaMin, etaMax, bShift); +} + +TimeframeSplitter::Result TimeframeSplitter::Unfold(const JEvent& parent, JEvent& child, + int child_idx) { + const float m_timeframeWidth = timeframeWidth(); + const float m_timesplitWidth = timesplitWidth(); + const double timeResolution_mpgd = timeResolution_MPGD(); + const double timeResolution_tof = timeResolution_ACLGad(); + const double timeResolution_emcal = timeResolution_EMCal(); + + bool m_bTrigger = false; + + // Materialize these output collections even when no timeslice triggers, + // so the output schema is available from the first event. + (void)m_eventHeader_outCol(); + (void)m_eventHeaderPhy_outCols(); + (void)m_eventHeaderBkg_outCols(); + + const auto trackerHitCollsIn = m_trackerHits_inCols(); + const auto caloRecHitCollsIn = m_calorimeterHit_inCols(); + const auto trkAssoCollsIn = m_trackerHitsAsso_inCols(); + const auto calrecAssoCollsIn = m_mcRecoCalorimeterHitAssociation_inCols(); + + // == s == Register hits of TOF and MPGD detectors in the time slice ================== + if (child_idx == 0) { + m_OrigTFCount++; + + // Association collections belong to the parent Timeslice and remain valid for all + // of its children. Build each lookup once per parent instead of rebuilding the full + // index for every triggered PhysicsEvent. + m_trkAssoIds.clear(); + m_trkAssoIds.reserve(trkAssoCollsIn.size()); + for (const auto* associations : trkAssoCollsIn) { + m_trkAssoIds.push_back(buildTrkAssoId(associations)); + } + + m_calAssoIds.clear(); + m_calAssoIds.reserve(calrecAssoCollsIn.size()); + for (const auto* associations : calrecAssoCollsIn) { + m_calAssoIds.push_back(buildCalAssoId(associations)); + } + + // == s == For MC Trigger Efficiency Estimation ~~~~~~~~ + m_vPhysCooTimes.clear(); + + double prevMCTime = -9999.0; // temp check mc particle times + for (const auto& mcparticle : *m_mcParticles_inCol()) { + if (mcparticle.getGeneratorStatus() != 1) + continue; + if (std::abs(prevMCTime - mcparticle.getTime()) < 50.) + continue; + double mcCollTime = mcparticle.getTime(); + m_vPhysCooTimes.push_back(mcCollTime); + prevMCTime = mcCollTime; + } + std::sort(m_vPhysCooTimes.begin(), m_vPhysCooTimes.end()); + auto last = std::unique(m_vPhysCooTimes.begin(), m_vPhysCooTimes.end()); + m_vPhysCooTimes.erase(last, m_vPhysCooTimes.end()); + // == e == For MC Trigger Efficiency Estimation ~~~~~~~~ + } + // == e == Register hits of TOF and MPGD detectors in the time slice ================== + + // == s == Time frame scan loop ========================================================== + double timesliceT0 = -999.0; + bool bTimesliceTrigger = false; + + bool bMutipliTriggers[6] = {false, false, false, false, false, false}; + double multipliTrigTime[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + + if (m_timesplitWidth <= 0.0F) { + throw std::runtime_error("TimeframeSplitter: timesplitWidth must be greater than zero"); + } + if (m_timeframeWidth <= 0.0F) { + throw std::runtime_error("TimeframeSplitter: timeframeWidth must be greater than zero"); + } + + const size_t nTimeSlices = static_cast(std::floor(m_timeframeWidth / m_timesplitWidth)); + double tsTimeS = 0.0; + double tsTimeE = 0.0; + + // Scan the timeframe one time slice at a time. + // The scan stops early when a physics trigger fires; otherwise it terminates + // after all time slices in the timeframe have been processed. + while (iTimeSlice < nTimeSlices) { + tsTimeS = iTimeSlice * m_timesplitWidth; + tsTimeE = (iTimeSlice + 1) * m_timesplitWidth; + if (tsTimeE > m_timeframeWidth) + break; + iTimeSlice++; + + // == s == Multiplisity threshold Triggers ======================================= + + // == s == Multiplisity Single Triggers ======================================= + std::array singleTrig{}; + std::array singleTrigTime{}; + + // s // EndCap Cal Trigger + EtaPhiGrid backEndCalGrid{}; + EtaPhiGrid backEndCalGridShifted{}; + EtaPhiTimeGrid backEndIntTimesEtaPhi = {}; + EtaPhiTimeGrid backEndIntTimesEtaPhiShifted = {}; + fillEtaPhiGrids(caloRecHitCollsIn.at(kCalEndcapN), iniCalHitPoint[kCalEndcapN], + timeResolution_emcal, tsTimeS, tsTimeE, backEndCalGrid, backEndCalGridShifted, + backEndIntTimesEtaPhi, backEndIntTimesEtaPhiShifted, backEndEtaPhiBins); + singleTrig[0] = + countGridCellsWithMultiplicity(backEndCalGrid, backEndCalGridShifted, backEndIntTimesEtaPhi, + backEndIntTimesEtaPhiShifted, 10, singleTrigTime[0]); + + // s // EndCap Cal+Trk Match Trigger + EtaPhiGrid backEndTrkGrid = {}; + EtaPhiGrid backEndTrkGridShifted = {}; + EtaPhiTimeGrid backEndIntTimesEtaPhiMatched = {}; + EtaPhiTimeGrid backEndIntTimesEtaPhiMatchedShifted = {}; + fillEtaPhiGridsMatched(trackerHitCollsIn.at(kTrkBackwardMPGD), iniTrkHitPoint[kTrkBackwardMPGD], + timeResolution_mpgd, tsTimeS, tsTimeE, backEndCalGrid, + backEndCalGridShifted, backEndTrkGrid, backEndTrkGridShifted, 10, + backEndIntTimesEtaPhiMatched, backEndIntTimesEtaPhiMatchedShifted, + backEndEtaPhiBins); + singleTrig[1] = countGridCellsWithMultiplicity( + backEndTrkGrid, backEndTrkGridShifted, backEndIntTimesEtaPhiMatched, + backEndIntTimesEtaPhiMatchedShifted, 1, singleTrigTime[1]); + + EtaPhiGrid barrelCalGrid = {}; + EtaPhiGrid barrelCalGridShifted = {}; + EtaPhiTimeGrid barrelIntTimesEtaPhi = {}; + EtaPhiTimeGrid barrelIntTimesEtaPhiShifted = {}; + fillEtaPhiGrids(caloRecHitCollsIn.at(kCalBarrelScifi), iniCalHitPoint[kCalBarrelScifi], + timeResolution_emcal, tsTimeS, tsTimeE, barrelCalGrid, barrelCalGridShifted, + barrelIntTimesEtaPhi, barrelIntTimesEtaPhiShifted, barrelEtaPhiBins); + singleTrig[2] = + countGridCellsWithMultiplicity(barrelCalGrid, barrelCalGridShifted, barrelIntTimesEtaPhi, + barrelIntTimesEtaPhiShifted, 10, singleTrigTime[2]); + + EtaPhiGrid barrelTrkGrid = {}; + EtaPhiGrid barrelTrkGridShifted = {}; + EtaPhiTimeGrid barrelIntTimesEtaPhiMatched = {}; + EtaPhiTimeGrid barrelIntTimesEtaPhiMatchedShifted = {}; + fillEtaPhiGridsMatched( + trackerHitCollsIn.at(kTrkMPGDBarrel), iniTrkHitPoint[kTrkMPGDBarrel], timeResolution_mpgd, + tsTimeS, tsTimeE, barrelCalGrid, barrelCalGridShifted, barrelTrkGrid, barrelTrkGridShifted, + 5, barrelIntTimesEtaPhiMatched, barrelIntTimesEtaPhiMatchedShifted, barrelEtaPhiBins); + fillEtaPhiGridsMatched(trackerHitCollsIn.at(kTrkOuterMPGDBarrel), + iniTrkHitPoint[kTrkOuterMPGDBarrel], timeResolution_mpgd, tsTimeS, + tsTimeE, barrelCalGrid, barrelCalGridShifted, barrelTrkGrid, + barrelTrkGridShifted, 5, barrelIntTimesEtaPhiMatched, + barrelIntTimesEtaPhiMatchedShifted, barrelEtaPhiBins); + fillEtaPhiGridsMatched( + trackerHitCollsIn.at(kTrkTOFBarrel), iniTrkHitPoint[kTrkTOFBarrel], timeResolution_tof, + tsTimeS, tsTimeE, barrelCalGrid, barrelCalGridShifted, barrelTrkGrid, barrelTrkGridShifted, + 5, barrelIntTimesEtaPhiMatched, barrelIntTimesEtaPhiMatchedShifted, barrelEtaPhiBins); + singleTrig[3] = countGridCellsWithMultiplicity( + barrelTrkGrid, barrelTrkGridShifted, barrelIntTimesEtaPhiMatched, + barrelIntTimesEtaPhiMatchedShifted, 1, singleTrigTime[3]); + + EtaPhiGrid frontEndCalGrid = {}; + EtaPhiGrid frontEndCalGridShifted = {}; + EtaPhiTimeGrid frontEndIntTimesEtaPhi = {}; + EtaPhiTimeGrid frontEndIntTimesEtaPhiShifted = {}; + fillEtaPhiGrids(caloRecHitCollsIn.at(kCalEndcapP), iniCalHitPoint[kCalEndcapP], + timeResolution_emcal, tsTimeS, tsTimeE, frontEndCalGrid, frontEndCalGridShifted, + frontEndIntTimesEtaPhi, frontEndIntTimesEtaPhiShifted, forwardEndEtaPhiBins); + singleTrig[4] = countGridCellsWithMultiplicity( + frontEndCalGrid, frontEndCalGridShifted, frontEndIntTimesEtaPhi, + frontEndIntTimesEtaPhiShifted, 10, singleTrigTime[4]); + + EtaPhiGrid frontEndTrkGrid = {}; + EtaPhiGrid frontEndTrkGridShifted = {}; + EtaPhiTimeGrid frontEndIntTimesEtaPhiMatched = {}; + EtaPhiTimeGrid frontEndIntTimesEtaPhiMatchedShifted = {}; + fillEtaPhiGridsMatched(trackerHitCollsIn.at(kTrkForwardMPGD), iniTrkHitPoint[kTrkForwardMPGD], + timeResolution_mpgd, tsTimeS, tsTimeE, frontEndCalGrid, + frontEndCalGridShifted, frontEndTrkGrid, frontEndTrkGridShifted, 5, + frontEndIntTimesEtaPhiMatched, frontEndIntTimesEtaPhiMatchedShifted, + forwardEndEtaPhiBins); + fillEtaPhiGridsMatched(trackerHitCollsIn.at(kTrkTOFEndcap), iniTrkHitPoint[kTrkTOFEndcap], + timeResolution_tof, tsTimeS, tsTimeE, frontEndCalGrid, + frontEndCalGridShifted, frontEndTrkGrid, frontEndTrkGridShifted, 5, + frontEndIntTimesEtaPhiMatched, frontEndIntTimesEtaPhiMatchedShifted, + forwardEndEtaPhiBins); + singleTrig[5] = countGridCellsWithMultiplicity( + frontEndTrkGrid, frontEndTrkGridShifted, frontEndIntTimesEtaPhiMatched, + frontEndIntTimesEtaPhiMatchedShifted, 1, singleTrigTime[5]); + + const auto hitsB0 = countHitsInTimeWindow(trackerHitCollsIn.at(kTrkB0), iniTrkHitPoint[kTrkB0], + timeResolution_tof, tsTimeS, tsTimeE); + iniTrkHitPoint[kTrkB0] = hitsB0.nextStartID; + singleTrig[6] = hitsB0.count; + singleTrigTime[6] = hitsB0.average_time(); + + double totalZDCEnergy = 0.0; + double totalZDCEnergyTime = 0.0; + const auto* recHitsZDCECal = caloRecHitCollsIn.at(kCalZDC); + if (recHitsZDCECal != nullptr) { + for (size_t iHit = iniCalHitPoint[kCalZDC]; iHit < recHitsZDCECal->size(); ++iHit) { + const auto& hit = recHitsZDCECal->at(iHit); + const double hitTime = hit.getTime(); + if (hitTime - timeResolution_emcal > tsTimeE) + break; + if (judgeHitInTimeSlice(hitTime, timeResolution_emcal, tsTimeS, tsTimeE)) { + totalZDCEnergy += hit.getEnergy(); + totalZDCEnergyTime += hit.getEnergy() * hitTime; + iniCalHitPoint[kCalZDC] = iHit; + } + } + } + singleTrig[7] = totalZDCEnergy; + singleTrigTime[7] = totalZDCEnergy > 0.0 ? totalZDCEnergyTime / totalZDCEnergy : 0.0; + + const double etaPhiCalTriggerSum = singleTrig[0] + singleTrig[2] + singleTrig[4]; + const double etaPhiCalTrkTriggerSum = singleTrig[1] + singleTrig[3] + singleTrig[5]; + bMutipliTriggers[0] = etaPhiCalTrkTriggerSum > 0 && singleTrig[6] > 4; + bMutipliTriggers[1] = etaPhiCalTrkTriggerSum > 0 && singleTrig[7] > 50; + bMutipliTriggers[2] = etaPhiCalTriggerSum > 0 && singleTrig[6] > 4; + bMutipliTriggers[3] = etaPhiCalTriggerSum > 0 && singleTrig[7] > 0.005; + bMutipliTriggers[4] = etaPhiCalTrkTriggerSum > 1; + bMutipliTriggers[5] = etaPhiCalTriggerSum > 2; + + if (!bMutipliTriggers[0] && !bMutipliTriggers[1] && !bMutipliTriggers[2] && + !bMutipliTriggers[3] && !bMutipliTriggers[4] && !bMutipliTriggers[5]) + continue; + + const double fallbackTriggerTime = 0.5 * (tsTimeS + tsTimeE); + if (bMutipliTriggers[0]) + multipliTrigTime[0] = + averageSelectedTriggerTime(singleTrig, singleTrigTime, {1, 3, 5, 6}, fallbackTriggerTime); + if (bMutipliTriggers[1]) + multipliTrigTime[1] = + averageSelectedTriggerTime(singleTrig, singleTrigTime, {1, 3, 5, 7}, fallbackTriggerTime); + if (bMutipliTriggers[2]) + multipliTrigTime[2] = + averageSelectedTriggerTime(singleTrig, singleTrigTime, {0, 2, 4, 6}, fallbackTriggerTime); + if (bMutipliTriggers[3]) + multipliTrigTime[3] = + averageSelectedTriggerTime(singleTrig, singleTrigTime, {0, 2, 4, 7}, fallbackTriggerTime); + if (bMutipliTriggers[4]) + multipliTrigTime[4] = + averageSelectedTriggerTime(singleTrig, singleTrigTime, {1, 3, 5}, fallbackTriggerTime); + if (bMutipliTriggers[5]) + multipliTrigTime[5] = + averageSelectedTriggerTime(singleTrig, singleTrigTime, {0, 2, 4}, fallbackTriggerTime); + double multiTrigCount = 0; + double totalTrigTime = 0.0; + for (size_t iTrig = 0; iTrig < 6; ++iTrig) { + if (bMutipliTriggers[iTrig]) { + totalTrigTime += multipliTrigTime[iTrig]; + multiTrigCount++; + } + } + timesliceT0 = multiTrigCount > 0 ? totalTrigTime / multiTrigCount : fallbackTriggerTime; + + // == s == Multiplisity Single Triggers ======================================= + + if (bMutipliTriggers[0] || bMutipliTriggers[1] || bMutipliTriggers[2] || bMutipliTriggers[3] || + bMutipliTriggers[4] || bMutipliTriggers[5]) + bTimesliceTrigger = + true; // ???? temporary, need to be removed after geometrical coincidence trigger is implemented + if (bTimesliceTrigger) + break; + } + // == e == Time frame scan loop ========================================================== + + m_bTrigger = bTimesliceTrigger; + if (bTimesliceTrigger) { + m_bOnceTriggered = true; + // For now, a one-to-one relationship between timeslices and events + child.SetEventNumber(m_NewEventCount); + child.SetRunNumber(parent.GetRunNumber()); + m_NewEventCount++; + // Clone truth particles before detector relations so every child SimTrackerHit can + // point to an MCParticle owned by this PhysicsEvent rather than by the parent Timeslice. + for (const auto& mcparticle : *m_mcParticles_inCol()) { + m_mcParticles_outCol()->push_back(mcparticle.clone(false)); + } + + // == s == Register Tracker Hits ======================================================= + for (size_t trkDetID = 0; trkDetID < trackerHitCollsIn.size(); ++trkDetID) { + const auto* trkCollIn = trackerHitCollsIn.at(trkDetID); + + if (trkCollIn == nullptr) + continue; + auto& trkCollOut = m_trackerHits_outCols().at(trkDetID); + const double detTimeReso = trkTimeResolution(trkDetID); + const auto* trkAssoCollIn = trkAssoCollsIn.at(trkDetID); + auto& rawCollOut = m_rawTrackerHit_outCols().at(trkDetID); + auto& trkAssoCollOut = m_trackerHitsAsso_outCols().at(trkDetID); + + for (size_t iHit = 0; iHit < trkCollIn->size(); ++iHit) { + const auto& trkHit = trkCollIn->at(iHit); + + const double hitT = trkHit.getTime(); + if (!overlapsTimeWindow(hitT, detTimeReso, timesliceT0 - 10., timesliceT0 + 30.)) { + continue; + } + + iniTrkHitPoint[trkDetID] = iHit; + copyTrkHitWithRelations(trkHit, trkAssoCollIn, m_trkAssoIds.at(trkDetID), trkCollOut, + rawCollOut, trkAssoCollOut, m_simTrackerHits_outCols().at(trkDetID), + m_recoTrackerHitLinks_outCols().at(trkDetID), + m_mcParticles_outCol()); + } + } + // == e == Register Tracker Hits ======================================================= + + // == s == Register Calo Rec Hits ======================================================= + for (size_t calDetID = 0; calDetID < caloRecHitCollsIn.size(); ++calDetID) { + const auto* caloInColl = caloRecHitCollsIn.at(calDetID); + if (caloInColl == nullptr) + continue; + auto& caloOutColl = m_calorimeterHit_outCols().at(calDetID); + + const auto* caloInCollAsso = calrecAssoCollsIn.at(calDetID); + if (caloInCollAsso == nullptr) + continue; + + for (size_t iCalHit = 0; iCalHit < caloInColl->size(); ++iCalHit) { + const auto& caloHit = caloInColl->at(iCalHit); + + double detTimeReso = timeResolution_emcal; + double hitT = caloHit.getTime(); + + if (hitT - detTimeReso > timesliceT0 + 30.) + continue; + if (overlapsTimeWindow(hitT, detTimeReso, timesliceT0 - 10., timesliceT0 + 30.)) { + auto copiedCaloHit = caloHit.clone(); + copiedCaloHit.setRawHit(edm4hep::RawCalorimeterHit()); + + const auto rawHitFromRec = caloHit.getRawHit(); + if (rawHitFromRec.isAvailable()) { + auto& rawCollOut = m_rawCalorimeterHit_outCols().at(calDetID); + auto copiedRawHit = rawHitFromRec.clone(); + rawCollOut->push_back(copiedRawHit); + copiedCaloHit.setRawHit(copiedRawHit); + + auto& assocCollOut = m_mcRecoCalorimeterHitAssociation_outCols().at(calDetID); + auto& linkCollOut = m_mcRecoCalorimeterHitLink_outCols().at(calDetID); + auto& simCollOut = m_simCalorimeterHit_outCols().at(calDetID); + const auto rawHitID = rawHitFromRec.getObjectID(); + + const auto& association_index = m_calAssoIds.at(calDetID); + const auto assocIterCal = association_index.find(objIdKey(rawHitID)); + + if (assocIterCal != association_index.end()) { + for (const size_t association_position : assocIterCal->second) { + const auto assoc = caloInCollAsso->at(association_position); + + if (!assoc.getSimHit().isAvailable()) + continue; + + auto copiedSimHit = assoc.getSimHit().clone(false); + simCollOut->push_back(copiedSimHit); + + auto copiedAssoc = assocCollOut->create(); + copiedAssoc.setWeight(assoc.getWeight()); + copiedAssoc.setRawHit(copiedRawHit); + copiedAssoc.setSimHit(copiedSimHit); + + auto copiedLink = linkCollOut->create(); + copiedLink.setWeight(assoc.getWeight()); + copiedLink.setFrom(copiedRawHit); + copiedLink.setTo(copiedSimHit); + } + } + } + + caloOutColl->push_back(copiedCaloHit); + iniCalHitPoint[calDetID] = iCalHit; + } + } + } + // == e == Register Calo Rec Hits ======================================================= + + // == s == For QA relation valuables QA<><><><><><><><><><><><><><><><><><>> + // == s == For MC Trigger Efficiency Estimation ~~~~~~~~ + unsigned int physEventWeight = 2; + for (auto it = m_vPhysCooTimes.begin(); it != m_vPhysCooTimes.end(); ++it) { + const double physCollTime = *it; + if ((physCollTime + 20 > timesliceT0 - 10) && (physCollTime - 10 < timesliceT0 + 30)) { + physEventWeight = 1; + m_vPhysCooTimes.erase(it); + break; + } + } + + if (physEventWeight == 1) { + edm4hep::MutableEventHeader eventHeader_bkg; + eventHeader_bkg.setRunNumber(m_eventNumber_TS * 10000 + child_idx); + eventHeader_bkg.setEventNumber(m_eventNumber_TS); + eventHeader_bkg.setTimeStamp(iTimeSlice); + eventHeader_bkg.setWeight(1); + m_eventHeaderBkg_outCols()->push_back(eventHeader_bkg); + + edm4hep::MutableEventHeader eventHeader_phy; + eventHeader_phy.setRunNumber(m_eventNumber_TS * 10000 + child_idx); + eventHeader_phy.setEventNumber(m_eventNumber_TS); + eventHeader_phy.setTimeStamp(iTimeSlice); + eventHeader_phy.setWeight(2); + m_eventHeaderPhy_outCols()->push_back(eventHeader_phy); + for (size_t iTrig = 0; iTrig < 6; ++iTrig) { + if (bMutipliTriggers[iTrig]) { + edm4hep::MutableEventHeader eventHeader_phy; + eventHeader_phy.setRunNumber(m_eventNumber_TS * 10000 + child_idx); + eventHeader_phy.setEventNumber(m_eventNumber_TS); + eventHeader_phy.setTimeStamp(iTimeSlice); + eventHeader_phy.setWeight(iTrig + 3); + m_eventHeaderPhy_outCols()->push_back(eventHeader_phy); + } + } + m_PhysCount++; + } else if (physEventWeight == 2) { + edm4hep::MutableEventHeader eventHeader_phy; + eventHeader_phy.setRunNumber(m_eventNumber_TS * 10000 + child_idx); + eventHeader_phy.setEventNumber(m_eventNumber_TS); + eventHeader_phy.setTimeStamp(iTimeSlice); + eventHeader_phy.setWeight(1); + m_eventHeaderPhy_outCols()->push_back(eventHeader_phy); + + edm4hep::MutableEventHeader eventHeader_bkg; + eventHeader_bkg.setRunNumber(m_eventNumber_TS * 10000 + child_idx); + eventHeader_bkg.setEventNumber(m_eventNumber_TS); + eventHeader_bkg.setTimeStamp(iTimeSlice); + eventHeader_bkg.setWeight(2); + m_eventHeaderBkg_outCols()->push_back(eventHeader_bkg); + for (size_t iTrig = 0; iTrig < 6; ++iTrig) { + if (bMutipliTriggers[iTrig]) { + edm4hep::MutableEventHeader eventHeader_bkg; + eventHeader_bkg.setRunNumber(m_eventNumber_TS * 10000 + child_idx); + eventHeader_bkg.setEventNumber(m_eventNumber_TS); + eventHeader_bkg.setTimeStamp(iTimeSlice); + eventHeader_bkg.setWeight(iTrig + 3); + m_eventHeaderBkg_outCols()->push_back(eventHeader_bkg); + } + } + } + m_eventNumber_TS++; + + // Insert an independent EventHeader object into the physics event. + // A subset header would keep a reference to the parent frame collection. + edm4hep::MutableEventHeader eventHeader; + if (m_eventHeader_inCol() != nullptr && !m_eventHeader_inCol()->empty()) { + const auto& eventHeader_in = m_eventHeader_inCol()->at(0); + eventHeader.setRunNumber(eventHeader_in.getRunNumber()); + eventHeader.setEventNumber(eventHeader_in.getEventNumber()); + eventHeader.setTimeStamp(eventHeader_in.getTimeStamp()); + eventHeader.setWeight(eventHeader_in.getWeight()); + } else { + eventHeader.setRunNumber(child.GetRunNumber()); + eventHeader.setEventNumber(child.GetEventNumber()); + eventHeader.setTimeStamp(iTimeSlice); + eventHeader.setWeight(physEventWeight); + } + m_eventHeader_outCol()->push_back(eventHeader); + // == e == For MC Trigger Efficiency Estimation ~~~~~~~~ + + // == s == For QA relation valuables QA<><><><><><><><><><><><><><><><><>> + } + + if (iTimeSlice >= nTimeSlices) + m_bScanedAllTimeWindows = true; + if (m_bScanedAllTimeWindows) { + bInitialLoop = true; + m_bOnceTriggered = false; + + m_vPhysCooTimes.clear(); + + m_bScanedAllTimeWindows = false; + iTimeSlice = 0; + targetDetId = 0; + for (auto& start_point : iniTrkHitPoint) + start_point = 0; + for (auto& start_point : iniCalHitPoint) + start_point = 0; + + if (m_bTrigger) + return Result::NextChildNextParent; + else + return Result::KeepChildNextParent; + } else if (m_bTrigger) { + child_idx++; + return Result::NextChildKeepParent; + } + return Result::KeepChildNextParent; +} diff --git a/src/global/splitting/TimeframeSplitter.h b/src/global/splitting/TimeframeSplitter.h new file mode 100644 index 0000000000..83d32f8e76 --- /dev/null +++ b/src/global/splitting/TimeframeSplitter.h @@ -0,0 +1,662 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright (C) 2026 Takuya Kumaoka + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct TimeframeSplitter : public JEventUnfolder { + + Parameter timeframeWidth{this, "timeframe_width", 2000.0, "Width of each timeframe in ns"}; + Parameter timesplitWidth{this, "timesplit_width", 20.0, "Width of each timeslice in ns"}; + Parameter timeResolution_SiMaps{this, "timeResolution_Silicon", 2000.0, + "time resolution of Silicon detector in ns"}; + Parameter timeResolution_MPGD{this, "timeResolution_MPGD", 30.0, + "time resolution of MPGD detector in ns"}; + Parameter timeResolution_ACLGad{this, "timeResolution_TOF", 20.0, + "time resolution of TOF detector in ns"}; + Parameter timeResolution_EMCal{this, "timeResolution_EMCal", 20.0, + "time resolution of EMCal detector in ns"}; + bool m_use_timeframe = false; // Use timeframes to split events, or use timeslices + + unsigned int m_OrigTFCount = 0; //QA + unsigned int m_NewEventCount = 0; //QA + unsigned int m_PhysCount = 0; //QA + + size_t m_eventNumber_TS = 0; // Event number for the current timeslice + std::vector m_vTargetEvent; // List of original event numbers for each timeslice + + static constexpr double kPi = 3.14159265358979323846; + + static constexpr int kEtaPhiBins = 10; + static constexpr int kInvalidEtaPhiBin = -1; + + using EtaPhiGrid = std::array, kEtaPhiBins>; + using EtaPhiTimeGrid = std::array, kEtaPhiBins>; + using EtaPhiEnergyGrid = std::array, kEtaPhiBins>; + + enum TrkCollectionType : size_t { + kTrackerHitAligned = 0, + kTrackerHit, + kTrackerHitAssociation, + kTrackerHitLink, + kSimTrackerHit, + kRawTrackerHit, + kTrkCollectionTypeSize + }; + + enum CalCollectionType : size_t { + kCalorimeterHitAligned = 0, + kCalorimeterHit, + kCalorimeterHitAssociation, + kCalorimeterHitLink, + kSimCalorimeterHit, + kRawCalorimeterHit, + kCalCollectionTypeSize + }; + + enum TrkCollectionIndex : size_t { + kTrkB0 = 0, + kTrkTOFBarrel = 1, + kTrkTOFEndcap = 2, + kTrkMPGDBarrel = 3, + kTrkOuterMPGDBarrel = 4, + kTrkBackwardMPGD = 5, + kTrkForwardMPGD = 6, + kTrkSiBarrelVertex = 7, + kTrkSiBarrel = 8, + kTrkSiEndcap = 9, + kTrkTagger = 10, + kTrkForwardRomanPot = 11, + kTrkForwardOffMTracker = 12 + }; + + enum CalRecCollectionIndex : size_t { + kCalB0ECal = 0, + kCalBarrelImg = 1, + kCalBarrelScifi = 2, + kCalEndcapN = 3, + kCalEndcapP = 4, + kCalZDC = 5, + kCalLumi = 6 + }; + + using trkCollNames = std::array; + std::array m_trkCollNames = {{ + { + "B0TrackerRecHits_aligned", + "B0TrackerRecHits", + "B0TrackerRawHitAssociations", + "B0TrackerRawHitLinks", + "B0TrackerHits", + "B0TrackerRawHits", + }, + { + "TOFBarrelRecHits_aligned", + "TOFBarrelRecHits", + "TOFBarrelRawHitAssociations", + "TOFBarrelRawHitLinks", + "TOFBarrelHits", + "TOFBarrelRawHits", + }, + { + "TOFEndcapRecHits_aligned", + "TOFEndcapRecHits", + "TOFEndcapRawHitAssociations", + "TOFEndcapRawHitLinks", + "TOFEndcapHits", + "TOFEndcapRawHits", + }, + { + "MPGDBarrelRecHits_aligned", + "MPGDBarrelRecHits", + "MPGDBarrelRawHitAssociations", + "MPGDBarrelRawHitLinks", + "MPGDBarrelHits", + "MPGDBarrelRawHits", + }, + { + "OuterMPGDBarrelRecHits_aligned", + "OuterMPGDBarrelRecHits", + "OuterMPGDBarrelRawHitAssociations", + "OuterMPGDBarrelRawHitLinks", + "OuterMPGDBarrelHits", + "OuterMPGDBarrelRawHits", + }, + { + "BackwardMPGDEndcapRecHits_aligned", + "BackwardMPGDEndcapRecHits", + "BackwardMPGDEndcapRawHitAssociations", + "BackwardMPGDEndcapRawHitLinks", + "BackwardMPGDEndcapHits", + "BackwardMPGDEndcapRawHits", + }, + { + "ForwardMPGDEndcapRecHits_aligned", + "ForwardMPGDEndcapRecHits", + "ForwardMPGDEndcapRawHitAssociations", + "ForwardMPGDEndcapRawHitLinks", + "ForwardMPGDEndcapHits", + "ForwardMPGDEndcapRawHits", + }, + { + "SiBarrelVertexRecHits_aligned", + "SiBarrelVertexRecHits", + "SiBarrelVertexRawHitAssociations", + "SiBarrelVertexRawHitLinks", + "VertexBarrelHits", + "SiBarrelVertexRawHits", + }, + { + "SiBarrelTrackerRecHits_aligned", + "SiBarrelTrackerRecHits", + "SiBarrelRawHitAssociations", + "SiBarrelRawHitLinks", + "SiBarrelHits", + "SiBarrelRawHits", + }, + { + "SiEndcapTrackerRecHits_aligned", + "SiEndcapTrackerRecHits", + "SiEndcapTrackerRawHitAssociations", + "SiEndcapTrackerRawHitLinks", + "TrackerEndcapHits", + "SiEndcapTrackerRawHits", + }, + { + "TaggerTrackerRecHits_aligned", + "TaggerTrackerRecHits", + "TaggerTrackerRawHitAssociations", + "TaggerTrackerRawHitLinks", + "TaggerTrackerHits", + "TaggerTrackerRawHits", + }, + { + "ForwardRomanPotRecHits_aligned", + "ForwardRomanPotRecHits", + "ForwardRomanPotRawHitAssociations", + "ForwardRomanPotRawHitLinks", + "ForwardRomanPotHits", + "ForwardRomanPotRawHits", + }, + { + "ForwardOffMTrackerRecHits_aligned", + "ForwardOffMTrackerRecHits", + "ForwardOffMTrackerRawHitAssociations", + "ForwardOffMTrackerRawHitLinks", + "ForwardOffMTrackerHits", + "ForwardOffMTrackerRawHits", + }, + }}; + // "RICHEndcapNRecHits_aligned" + // "DIRCBarRecHits_aligned", + // "DRICHRecHits_aligned", + + using calCollNames = std::array; + std::array m_calCollNames = {{ + { + "B0ECalRecHits_aligned", + "B0ECalRecHits", + "B0ECalRawHitAssociations", + "B0ECalRawHitLinks", + "B0ECalHits", + "B0ECalRawHits", + }, + { + "EcalBarrelImagingRecHits_aligned", + "EcalBarrelImagingRecHits", + "EcalBarrelImagingRawHitAssociations", + "EcalBarrelImagingRawHitLinks", + "EcalBarrelImagingHits", + "EcalBarrelImagingRawHits", + }, + { + "EcalBarrelScFiRecHits_aligned", + "EcalBarrelScFiRecHits", + "EcalBarrelScFiRawHitAssociations", + "EcalBarrelScFiRawHitLinks", + "EcalBarrelScFiHits", + "EcalBarrelScFiRawHits", + }, + { + "EcalEndcapNRecHits_aligned", + "EcalEndcapNRecHits", + "EcalEndcapNRawHitAssociations", + "EcalEndcapNRawHitLinks", + "EcalEndcapNHits", + "EcalEndcapNRawHits", + }, + { + "EcalEndcapPRecHits_aligned", + "EcalEndcapPRecHits", + "EcalEndcapPRawHitAssociations", + "EcalEndcapPRawHitLinks", + "EcalEndcapPHits", + "EcalEndcapPRawHits", + }, + { + "EcalFarForwardZDCRecHits_aligned", + "EcalFarForwardZDCRecHits", + "EcalFarForwardZDCRawHitAssociations", + "EcalFarForwardZDCRawHitLinks", + "EcalFarForwardZDCHits", + "EcalFarForwardZDCRawHits", + }, + { + "EcalLumiSpecRecHits_aligned", + "EcalLumiSpecRecHits", + "EcalLumiSpecRawHitAssociations", + "EcalLumiSpecRawHitLinks", + "EcalLumiSpecHits", + "EcalLumiSpecRawHits", + }, + { + "HcalBarrelRecHits_aligned", + "HcalBarrelRecHits", + "HcalBarrelRawHitAssociations", + "HcalBarrelRawHitLinks", + "HcalBarrelHits", + "HcalBarrelRawHits", + }, + { + "HcalEndcapNRecHits_aligned", + "HcalEndcapNRecHits", + "HcalEndcapNRawHitAssociations", + "HcalEndcapNRawHitLinks", + "HcalEndcapNHits", + "HcalEndcapNRawHits", + }, + { + "HcalEndcapPInsertRecHits_aligned", + "HcalEndcapPInsertRecHits", + "HcalEndcapPInsertRawHitAssociations", + "HcalEndcapPInsertRawHitLinks", + "HcalEndcapPInsertHits", + "HcalEndcapPInsertRawHits", + }, + { + "HcalFarForwardZDCRecHits_aligned", + "HcalFarForwardZDCRecHits", + "HcalFarForwardZDCRawHitAssociations", + "HcalFarForwardZDCRawHitLinks", + "HcalFarForwardZDCHits", + "HcalFarForwardZDCRawHits", + }, + { + "LFHCALRecHits_aligned", + "LFHCALRecHits", + "LFHCALRawHitAssociations", + "LFHCALRawHitLinks", + "LFHCALHits", + "LFHCALRawHits", + }, + }}; + + std::vector getTrkCollectionNames(TrkCollectionType type) const { + std::vector names; + names.reserve(m_trkCollNames.size()); + for (const auto& collections : m_trkCollNames) { + names.push_back(collections[type]); + } + return names; + } + + std::vector getCalCollectionNames(CalCollectionType type) const { + std::vector names; + names.reserve(m_calCollNames.size()); + for (const auto& collections : m_calCollNames) { + names.push_back(collections[type]); + } + return names; + } + + PodioInput m_eventHeader_inCol{ + this, {.name = "EventHeader", .is_optional = true}}; + PodioOutput m_eventHeader_outCol{this, "EventHeader"}; + + PodioInput m_mcParticles_inCol{this, {.name = "MCParticles"}}; + PodioOutput m_mcParticles_outCol{this, "MCParticles"}; + + // tracker collections + VariadicPodioInput m_trackerHits_inCols{ + this, {.names = getTrkCollectionNames(kTrackerHitAligned), .is_optional = true}}; + VariadicPodioOutput m_trackerHits_outCols{ + this, getTrkCollectionNames(kTrackerHit)}; + + VariadicPodioInput m_trackerHitsAsso_inCols{ + this, {.names = getTrkCollectionNames(kTrackerHitAssociation), .is_optional = true}}; + VariadicPodioOutput m_trackerHitsAsso_outCols{ + this, getTrkCollectionNames(kTrackerHitAssociation)}; + + // VariadicPodioOutput m_recoTrackerHitLinks_inCols{ + // this, getTrkCollectionNames(kTrackerHitLink)}; + VariadicPodioOutput m_recoTrackerHitLinks_outCols{ + this, getTrkCollectionNames(kTrackerHitLink)}; + + VariadicPodioOutput m_simTrackerHits_outCols{ + this, getTrkCollectionNames(kSimTrackerHit)}; + + VariadicPodioInput m_rawTrackerHit_inCols{ + this, {.names = getTrkCollectionNames(kRawTrackerHit), .is_optional = true}}; + VariadicPodioOutput m_rawTrackerHit_outCols{ + this, getTrkCollectionNames(kRawTrackerHit)}; + + // calorimeter collections + VariadicPodioInput m_rawCalorimeterHit_inCols{ + this, {.names = getCalCollectionNames(kRawCalorimeterHit), .is_optional = true}}; + VariadicPodioOutput m_rawCalorimeterHit_outCols{ + this, getCalCollectionNames(kRawCalorimeterHit)}; + + VariadicPodioOutput m_mcRecoCalorimeterHitLink_outCols{ + this, getCalCollectionNames(kCalorimeterHitLink)}; + VariadicPodioOutput m_simCalorimeterHit_outCols{ + this, getCalCollectionNames(kSimCalorimeterHit)}; + + VariadicPodioInput m_calorimeterHit_inCols{ + this, {.names = getCalCollectionNames(kCalorimeterHitAligned), .is_optional = true}}; + VariadicPodioOutput m_calorimeterHit_outCols{ + this, getCalCollectionNames(kCalorimeterHit)}; + + VariadicPodioInput + m_mcRecoCalorimeterHitAssociation_inCols{ + this, {.names = getCalCollectionNames(kCalorimeterHitAssociation), .is_optional = true}}; + VariadicPodioOutput + m_mcRecoCalorimeterHitAssociation_outCols{this, + getCalCollectionNames(kCalorimeterHitAssociation)}; + + PodioOutput m_eventHeaderPhy_outCols{this, "EventHeader_PHY"}; + PodioOutput m_eventHeaderBkg_outCols{this, "EventHeader_BKG"}; + + TimeframeSplitter(); + + std::vector> + m_hitStartIndices_simTracker; + std::vector> + m_hitStartIndices_simCalorimeter; + + // == Global Variables ======================= + bool bInitialLoop = true; + + int m_multiTriggerThreshold[4] = {1, 4, 20, 20}; + size_t iniTrkHitPoint[15] = {0}; // B0Trk, + size_t iniCalHitPoint[15] = {0}; // B0Trk, + bool m_bDetLastHits[10] = {false, false, false, false, false, false, false, false, false, false}; + + bool m_bOnceTriggered = false; + bool m_bScanedAllTimeWindows = false; + + unsigned int targetDetId = 0; + size_t iTimeSlice = 0; + std::vector m_vPhysCooTimes = {}; + // == Global Variables ======================= + + struct TimeWindowSummary { + size_t count = 0; + double timeSum = 0.0; + size_t nextStartID = 0; + + double average_time() const { return count == 0 ? 0.0 : timeSum / count; } + }; + + using TrackerAssociationIndex = std::unordered_map>; + using CalorimeterAssociationIndex = std::unordered_map>; + + std::vector m_trkAssoIds; + std::vector m_calAssoIds; + + static std::uint64_t objIdKey(const podio::ObjectID& object_id); + + static TrackerAssociationIndex + buildTrkAssoId(const edm4eic::MCRecoTrackerHitAssociationCollection* associations); + + static CalorimeterAssociationIndex + buildCalAssoId(const edm4eic::MCRecoCalorimeterHitAssociationCollection* associations); + + static bool overlapsTimeWindow(double hitTime, double resolution, double window_start, + double window_end); + static bool judgeOverTimeWindow(double hitTime, double resolution, double window_end); + + static bool isValidEtaPhiBin(int etaBin, int phiBin); + + static bool judgeHitInTimeSlice(double hitTime, double timeResolution, double timeslice_start, + double timeslice_end); + + template inline void etaPhiCalc(const HitT& hit, double& hitEta, double& hitPhi) { + const double hitX = hit.getPosition()[0]; + const double hitY = hit.getPosition()[1]; + const double hitZ = hit.getPosition()[2]; + const double hitR = std::sqrt(hitX * hitX + hitY * hitY + hitZ * hitZ); + if (hitR <= 0.0) { + hitEta = 0.0; + hitPhi = 0.0; + return; + } + + const double cosTheta = std::clamp(hitZ / hitR, -1.0, 1.0); + const double hitTheta = std::acos(cosTheta); + + hitEta = -std::log(std::tan(hitTheta / 2.0)); + hitPhi = std::atan2(hitY, hitX); + } + + static std::pair etaPhiBins(double hitEta, double hitPhi, double etaMin, double etaMax, + int bShift); + + template + void fillEtaPhiGrids(const CollectionT* hits, size_t& iniHitID, double timeResolution, + double timeSliceStart, double timeSliceEnd, EtaPhiGrid& grid, + EtaPhiGrid& gridShifted, EtaPhiTimeGrid& gridTime, + EtaPhiTimeGrid& gridShiftedTime, BinFunc binFunc) { + if (hits == nullptr) + return; + + const size_t hitCount = hits->size(); + for (size_t iHit = iniHitID; iHit < hitCount; ++iHit) { + const auto& hit = hits->at(iHit); + const double hitT = hit.getTime(); + if (hitT - timeResolution > timeSliceEnd) { + iniHitID = iHit; + break; + } + if (!judgeHitInTimeSlice(hitT, timeResolution, timeSliceStart, timeSliceEnd)) + continue; + + double hitEta = 0.0; + double hitPhi = 0.0; + etaPhiCalc(hit, hitEta, hitPhi); + + const auto [eta0, phi0] = binFunc(hitEta, hitPhi, 0); + const auto [eta1, phi1] = binFunc(hitEta, hitPhi, 1); + if (isValidEtaPhiBin(eta0, phi0)) { + grid[eta0][phi0]++; + gridTime[eta0][phi0] += hitT; + } + if (isValidEtaPhiBin(eta1, phi1)) { + gridShifted[eta1][phi1]++; + gridShiftedTime[eta1][phi1] += hitT; + } + } + } + + template + void fillEtaPhiGridsMatched(const CollectionT* collection, size_t& iniHitID, + double timeResolution, double timeSliceStart, double timeSliceEnd, + const EtaPhiGrid& baseGrid, const EtaPhiGrid& baseGridShifted, + EtaPhiGrid& compGrid, EtaPhiGrid& compGridShifted, int baseThreshold, + EtaPhiTimeGrid& compGridTime, EtaPhiTimeGrid& compGridShiftedTime, + BinFunc binFunc) { + if (collection == nullptr) + return; + + const size_t hitCount = collection->size(); + for (size_t iHit = iniHitID; iHit < hitCount; ++iHit) { + const double hitT = collection->at(iHit).getTime(); + if (hitT - timeResolution > timeSliceEnd) { + iniHitID = iHit; + break; + } + if (!judgeHitInTimeSlice(hitT, timeResolution, timeSliceStart, timeSliceEnd)) + continue; + + double hitEta = 0.0; + double hitPhi = 0.0; + etaPhiCalc(collection->at(iHit), hitEta, hitPhi); + + const auto [eta0, phi0] = binFunc(hitEta, hitPhi, 0); + const auto [eta1, phi1] = binFunc(hitEta, hitPhi, 1); + if (isValidEtaPhiBin(eta0, phi0) && baseGrid[eta0][phi0] >= baseThreshold) { + compGrid[eta0][phi0]++; + compGridTime[eta0][phi0] += hitT; + } + if (isValidEtaPhiBin(eta1, phi1) && baseGridShifted[eta1][phi1] >= baseThreshold) { + compGridShifted[eta1][phi1]++; + compGridShiftedTime[eta1][phi1] += hitT; + } + } + } + + static size_t countGridCellsWithMultiplicity(const EtaPhiGrid& grid0, + const EtaPhiGrid& gridShifted, + const EtaPhiTimeGrid& gridTime0, + const EtaPhiTimeGrid& gridShiftedTime, int threshold, + double& averageTime); + + static double averageSelectedTriggerTime(const std::array& values, + const std::array& times, + std::initializer_list indices, + double fallbackTime); + + double trkTimeResolution(size_t detectorID); + + template + TimeWindowSummary countHitsInTimeWindow(const CollectionT* collection, size_t startHitID, + double resolution, double window_start, + double window_end) const { + TimeWindowSummary summary; + summary.nextStartID = startHitID; + if (collection == nullptr) + return summary; + + for (size_t i = startHitID; i < collection->size(); ++i) { + const auto& hit = collection->at(i); + const double hitTime = hit.getTime(); + if (judgeOverTimeWindow(hitTime, resolution, window_end)) + break; + + // Drop hits which cannot overlap the next window (which begins at window_end) + if (hitTime + resolution < window_end) { + summary.nextStartID = i + 1; + } + + if (overlapsTimeWindow(hitTime, resolution, window_start, window_end)) { + ++summary.count; + summary.timeSum += hitTime; + } + } + return summary; + } + + template + static void + copyTrkHitWithRelations(const edm4eic::TrackerHit& trackerHit, + const edm4eic::MCRecoTrackerHitAssociationCollection* associations, + const TrackerAssociationIndex& association_index, + TrackerHitOutputT& trackerHits_out, RawHitOutputT& rawHits_out, + AssociationOutputT& associations_out, + std::unique_ptr& simHits_out, + std::unique_ptr& links_out, + std::unique_ptr& mc_particles_out) { + + auto trackerHitCopied = trackerHit.clone(); + trackerHitCopied.setRawHit(edm4eic::RawTrackerHit()); + + if (associations == nullptr || !trackerHit.getRawHit().isAvailable()) { + trackerHits_out->push_back(trackerHitCopied); + return; + } + + const auto rawHitId = trackerHit.getRawHit().getObjectID(); + + auto rawHitCopied = trackerHit.getRawHit().clone(); + rawHits_out->push_back(rawHitCopied); + trackerHitCopied.setRawHit(rawHitCopied); + + const auto assocIterCal = association_index.find(objIdKey(rawHitId)); + + if (assocIterCal != association_index.end()) { + for (const size_t index : assocIterCal->second) { + const auto association = associations->at(index); + + if (!association.getSimHit().isAvailable()) + continue; + + const auto simHit = association.getSimHit(); + auto simHitCopied = simHit.clone(false); + + if (simHit.getParticle().isAvailable()) { + const auto mcPIndex = simHit.getParticle().getObjectID(); + + if (mcPIndex.index >= 0 && + static_cast(mcPIndex.index) < mc_particles_out->size()) { + simHitCopied.setParticle((*mc_particles_out)[mcPIndex.index]); + } + } + + simHits_out->push_back(simHitCopied); + + auto copiedAsso = associations_out->create(); + copiedAsso.setWeight(association.getWeight()); + copiedAsso.setRawHit(rawHitCopied); + copiedAsso.setSimHit(simHitCopied); + + auto copiedLink = links_out->create(); + copiedLink.setWeight(association.getWeight()); + copiedLink.setFrom(rawHitCopied); + copiedLink.setTo(simHitCopied); + } + } + + trackerHits_out->push_back(trackerHitCopied); + } + + static std::pair backEndEtaPhiBins(double hitEta, double hitPhi, int bShift); + + static std::pair barrelEtaPhiBins(double hitEta, double hitPhi, int bShift); + + static std::pair forwardEndEtaPhiBins(double hitEta, double hitPhi, int bShift); + + Result Unfold(const JEvent& parent, JEvent& child, int child_idx) override; +}; diff --git a/src/global/splitting/splitting.cc b/src/global/splitting/splitting.cc new file mode 100644 index 0000000000..d8a194fdf0 --- /dev/null +++ b/src/global/splitting/splitting.cc @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright (C) 2026 Takuya Kumaoka + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "TimeframeSplitter.h" +#include "extensions/jana/JOmniFactoryGeneratorT.h" +#include "factories/event_building/HitTimeAlignment_factory.h" + +extern "C" { +void InitPlugin(JApplication* app) { + + const std::vector> trkHitTimeCollectionNames = { + {"TOFBarrelRecHits", "TOFBarrelRecHits_aligned"}, + {"TOFEndcapRecHits", "TOFEndcapRecHits_aligned"}, + {"MPGDBarrelRecHits", "MPGDBarrelRecHits_aligned"}, + {"OuterMPGDBarrelRecHits", "OuterMPGDBarrelRecHits_aligned"}, + {"BackwardMPGDEndcapRecHits", "BackwardMPGDEndcapRecHits_aligned"}, + {"ForwardMPGDEndcapRecHits", "ForwardMPGDEndcapRecHits_aligned"}, + {"SiBarrelVertexRecHits", "SiBarrelVertexRecHits_aligned"}, + {"SiBarrelTrackerRecHits", "SiBarrelTrackerRecHits_aligned"}, + {"SiEndcapTrackerRecHits", "SiEndcapTrackerRecHits_aligned"}, + {"B0TrackerRecHits", "B0TrackerRecHits_aligned"}, + {"TaggerTrackerRecHits", "TaggerTrackerRecHits_aligned"}, + {"ForwardRomanPotRecHits", "ForwardRomanPotRecHits_aligned"}, + {"ForwardOffMTrackerRecHits", "ForwardOffMTrackerRecHits_aligned"}, + }; + + const std::vector> calHitTimeCollectionNames = { + {"B0ECalRecHits", "B0ECalRecHits_aligned"}, + {"EcalBarrelImagingRecHits", "EcalBarrelImagingRecHits_aligned"}, + {"EcalBarrelScFiRecHits", "EcalBarrelScFiRecHits_aligned"}, + {"EcalEndcapNRecHits", "EcalEndcapNRecHits_aligned"}, + {"EcalEndcapPRecHits", "EcalEndcapPRecHits_aligned"}, + {"EcalFarForwardZDCRecHits", "EcalFarForwardZDCRecHits_aligned"}, + {"EcalLumiSpecRecHits", "EcalLumiSpecRecHits_aligned"}, + {"HcalBarrelRecHits", "HcalBarrelRecHits_aligned"}, + {"HcalEndcapNRecHits", "HcalEndcapNRecHits_aligned"}, + {"HcalEndcapPInsertRecHits", "HcalEndcapPInsertRecHits_aligned"}, + {"HcalFarForwardZDCRecHits", "HcalFarForwardZDCRecHits_aligned"}, + {"LFHCALRecHits", "LFHCALRecHits_aligned"}, + }; + + InitJANAPlugin(app); + + const bool splitTimeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + if (!splitTimeframes) { + return; + } + + for (const auto& [inName, outName] : trkHitTimeCollectionNames) { + app->Add(new JOmniFactoryGeneratorT>( + outName, {inName}, {outName}, app, JEventLevel::Timeslice)); + } + + for (const auto& [inName, outName] : calHitTimeCollectionNames) { + app->Add( + new JOmniFactoryGeneratorT>( + outName, {inName}, {outName}, app, JEventLevel::Timeslice)); + } + + // Unfolder that takes timeframes and splits them into physics events. + app->Add(new TimeframeSplitter()); +} +} // "C" diff --git a/src/services/io/podio/JEventProcessorPODIO.cc b/src/services/io/podio/JEventProcessorPODIO.cc index 4db9a2c1e2..a882660334 100644 --- a/src/services/io/podio/JEventProcessorPODIO.cc +++ b/src/services/io/podio/JEventProcessorPODIO.cc @@ -520,7 +520,11 @@ JEventProcessorPODIO::JEventProcessorPODIO() { "BarrelChargedCandidateParticlesAlpha", "EndcapPChargedCandidateParticlesAlpha", "EndcapPInsertChargedCandidateParticlesAlpha", + + "EventHeader_PHY", + "EventHeader_BKG", }; + std::vector output_exclude_collections; // need to get as vector, then convert to set japp->SetDefaultParameter( "podio:output_collections", output_collections, diff --git a/src/services/io/podio/podio.cc b/src/services/io/podio/podio.cc index 47622f0609..766cb68fcc 100644 --- a/src/services/io/podio/podio.cc +++ b/src/services/io/podio/podio.cc @@ -3,9 +3,11 @@ // // +#include #include #include #include +#include #include #include "JEventProcessorManagedPODIO.h" @@ -13,17 +15,33 @@ #include "JEventSourceManagedPODIO.h" #include "JEventSourcePODIO.h" +namespace { + +template +class LeveledEventSourceGeneratorT : public JEventSourceGeneratorT { +public: + explicit LeveledEventSourceGeneratorT(JEventLevel level) { this->SetLevel(level); } +}; + +} // namespace + // Make this a JANA plugin extern "C" { void InitPlugin(JApplication* app) { InitJANAPlugin(app); + const bool split_timeframes = + app->RegisterParameter("split_timeframes", false, "Enable timeframe splitting"); + const auto source_level = split_timeframes ? JEventLevel::Timeslice : JEventLevel::PhysicsEvent; + // Check if managed mode is requested if (app->GetJParameterManager()->Exists("podio:managed_socket_path")) { - app->Add(new JEventSourceManagedPODIO("", app)); + auto* source = new JEventSourceManagedPODIO("", app); + source->SetLevel(source_level); + app->Add(source); app->Add(new JEventProcessorManagedPODIO()); } else { - app->Add(new JEventSourceGeneratorT()); + app->Add(new LeveledEventSourceGeneratorT(source_level)); app->Add(new JEventProcessorPODIO()); } } diff --git a/src/utilities/eicrecon/eicrecon.cc b/src/utilities/eicrecon/eicrecon.cc index 34a4757c84..05320613de 100644 --- a/src/utilities/eicrecon/eicrecon.cc +++ b/src/utilities/eicrecon/eicrecon.cc @@ -50,6 +50,9 @@ std::vector EICRECON_DEFAULT_PLUGINS = { "ECTOF", "LOWQ2", "LUMISPECCAL", +#if defined(JANA_VERSION_MAJOR) && JANA_VERSION_MAJOR >= 2026 + "splitting", +#endif "podio", "janatop", // clang-format on