From 59bdd9b6d1d0c1032b9f99934c3ba95e678faf46 Mon Sep 17 00:00:00 2001 From: Matthias Kleiner Date: Mon, 19 Oct 2026 17:32:43 +0200 Subject: [PATCH] Cosmics cluster collector: store TPC occupancy per cosmic and per TF For each cosmic, tpcOccBef and tpcOccAft count the TPC clusters in the orbit before and after the cosmic's time (TOF time if available, otherwise the matcher's time), taken from the TPC occupancy map. They are -1 if no map is available or if the window extends beyond the TF. CosmicsTFInfo::tpcOcc stores the TF average of TPC clusters per orbit. Co-Authored-By: Claude Opus 5.5 --- .../TrackCosmicsExtended.h | 14 +++--- .../src/CosmicsClusterCollectorSpec.cxx | 44 +++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/DataFormats/Detectors/GlobalTracking/include/DataFormatsGlobalTracking/TrackCosmicsExtended.h b/DataFormats/Detectors/GlobalTracking/include/DataFormatsGlobalTracking/TrackCosmicsExtended.h index a6fbbcc969052..d28a5134ef6ea 100644 --- a/DataFormats/Detectors/GlobalTracking/include/DataFormatsGlobalTracking/TrackCosmicsExtended.h +++ b/DataFormats/Detectors/GlobalTracking/include/DataFormatsGlobalTracking/TrackCosmicsExtended.h @@ -110,16 +110,20 @@ struct TrackCosmicsExtended { ///< approach to the beam line; valid if chi2MatchPolished >= 0 float chi2MatchPolished = -1.f; ///< chi2 of the polished bottom and top halves at the closest approach (< 0: not polished) o2::MCCompLabel label{}; ///< MC label of the cosmic (MC only) + float tpcOccBef = -1.f; ///< TPC clusters in the orbit before the time of the cosmic (TOF time if hasTOFTime(), else the matcher's), + ///< from the TPC occupancy map (-1: no map, or the orbit is not fully inside the TF) + float tpcOccAft = -1.f; ///< same for the orbit after the time of the cosmic, i.e. while its clusters drift bool hasTOFTime() const { return scoreTOFPair >= 0.f; } ///< a HitTOFFlight pair gave the cosmic its time timeTOFMUS - ClassDefNV(TrackCosmicsExtended, 1); + ClassDefNV(TrackCosmicsExtended, 2); }; -/// per-TF quantities of the TPC transformation used in the reconstruction +/// per-TF quantities: the TPC transformation used in the reconstruction and the TPC occupancy struct CosmicsTFInfo { - float vDrift = 0.f; ///< drift velocity of the transformation [cm/time bin] - float t0 = 0.f; ///< time offset of the transformation [time bins] - ClassDefNV(CosmicsTFInfo, 1); + float vDrift = 0.f; ///< drift velocity of the transformation [cm/time bin] + float t0 = 0.f; ///< time offset of the transformation [time bins] + float tpcOcc = -1.f; ///< TPC clusters per orbit, average of the TF (TPC occupancy map) (< 0: no map) + ClassDefNV(CosmicsTFInfo, 2); }; } // namespace o2::dataformats diff --git a/Detectors/GlobalTrackingWorkflow/src/CosmicsClusterCollectorSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/CosmicsClusterCollectorSpec.cxx index c91318243ec0d..b0d129bb8a7fc 100644 --- a/Detectors/GlobalTrackingWorkflow/src/CosmicsClusterCollectorSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/CosmicsClusterCollectorSpec.cxx @@ -166,6 +166,8 @@ class CosmicsClusterCollectorSpec : public Task }; void updateTimeDependentParams(ProcessingContext& pc); + void fillTPCOccupancy(gsl::span occupancyMap); + float tpcOccupancy(float timeTB, bool after) const; void buildUsedMap(const RecoContainer& data, std::vector>& time0Windows); void addTPCAttached(const RecoContainer& data, const o2::tpc::TrackTPC& trk, std::vector& out, std::unordered_set& taken) const; /// time frame of a TPC road: the leg's z is shifted by dz and its clusters are transformed with vertexTime @@ -221,6 +223,10 @@ class CosmicsClusterCollectorSpec : public Task float mRoadITS = 1.5f; ///< road half-width in the ITS [cm] std::vector> mITSChipCentres; ///< global positions of the ITS chip centres (aligned geometry) float mTPCTBinMUS = 0.2f; ///< TPC time bin [mus] + std::vector mTPCOccCumul; ///< TPC occupancy map of the TF summed up: [i] = clusters in the bins before bin i + float mTPCOccBinTB = 16.f; ///< time bins per bin of the TPC occupancy map + int mTPCOccBinsPerOrbit = 1; ///< bins of the TPC occupancy map per orbit + float mTPCOccTF = -1.f; ///< TPC clusters per orbit, average of the TF (< 0: no occupancy map) float mBz = 0.f; bool mUseMC = false; mutable bool mStaggeredWarned = false; @@ -305,6 +311,8 @@ void CosmicsClusterCollectorSpec::run(ProcessingContext& pc) o2::dataformats::CosmicsTFInfo tfInfo; tfInfo.vDrift = mCorrMap->getVDrift(); tfInfo.t0 = mCorrMap->getT0(); + fillTPCOccupancy(recoData.occupancyMapTPC); + tfInfo.tpcOcc = mTPCOccTF; std::vector cosmicsOut; const auto cosmics = recoData.getCosmicTracks(); @@ -391,6 +399,8 @@ void CosmicsClusterCollectorSpec::run(ProcessingContext& pc) roadTime.known = true; } mDbgTauC = roadTime.tb; + out.tpcOccBef = tpcOccupancy(roadTime.tb, false); + out.tpcOccAft = tpcOccupancy(roadTime.tb, true); std::unordered_set taken; for (int leg = 0; leg < 2; leg++) { // attached clusters of both legs first, so that a road never takes the other leg's clusters if (tpcLegs[leg]) { @@ -488,6 +498,40 @@ void CosmicsClusterCollectorSpec::buildUsedMap(const RecoContainer& data, std::v } } +void CosmicsClusterCollectorSpec::fillTPCOccupancy(gsl::span occupancyMap) +{ + // TPC occupancy map (GPUChainTrackingSectorTracker): [0] clusters per orbit (average of the TF), [1] time bins per bin << 16 | averaging, + // [2...] clusters per bin (all clusters, by their time) + mTPCOccCumul.clear(); + mTPCOccTF = occupancyMap.empty() ? -1.f : occupancyMap[0]; + if (occupancyMap.size() < 3 || (occupancyMap[1] >> 16) == 0) { // no time bins in the map + return; + } + mTPCOccBinTB = occupancyMap[1] >> 16; + mTPCOccBinsPerOrbit = std::max(1, static_cast(o2::constants::lhc::LHCMaxBunches / 8 / mTPCOccBinTB)); + mTPCOccCumul.resize(occupancyMap.size() - 1, 0); + for (size_t bin = 2; bin < occupancyMap.size(); bin++) { + mTPCOccCumul[bin - 1] = mTPCOccCumul[bin - 2] + occupancyMap[bin]; + } +} + +float CosmicsClusterCollectorSpec::tpcOccupancy(float timeTB, bool after) const +{ + // TPC clusters in the map bins of one orbit before the bin of timeTB (after: from the bin of timeTB on); -1 if the window is not + // fully inside the TF, where the map would understate the occupancy + if (mTPCOccCumul.empty()) { + return -1.f; + } + const int nBins = static_cast(mTPCOccCumul.size()) - 1; + const int bin = static_cast(std::floor(timeTB / mTPCOccBinTB)); + const int first = after ? bin : bin - mTPCOccBinsPerOrbit; + const int last = after ? bin + mTPCOccBinsPerOrbit : bin; + if (first < 0 || last > nBins) { + return -1.f; + } + return static_cast(mTPCOccCumul[last] - mTPCOccCumul[first]); +} + void CosmicsClusterCollectorSpec::addTPCAttached(const RecoContainer& data, const o2::tpc::TrackTPC& trk, std::vector& out, std::unordered_set& taken) const { const auto& clusters = data.getTPCClusters();