From 1a83e8accfd5be1b550604deb160be12899c9217 Mon Sep 17 00:00:00 2001 From: Igor Altsybeev Date: Wed, 11 Nov 2026 11:17:55 +0100 Subject: [PATCH 01/14] add BC occupancy tables added BC occupancy configurables and updated occupancy calculation logic to include new BC occupancy structures. --- Common/Tools/EventSelectionModule.h | 321 +++++++++++++++++++++++----- 1 file changed, 270 insertions(+), 51 deletions(-) diff --git a/Common/Tools/EventSelectionModule.h b/Common/Tools/EventSelectionModule.h index 0d4fda380b0..eba03d8a34f 100644 --- a/Common/Tools/EventSelectionModule.h +++ b/Common/Tools/EventSelectionModule.h @@ -127,6 +127,20 @@ struct EvselConfigurables : o2::framework::ConfigurableGroup { o2::framework::Configurable confLightIonsMarginVzDiff{"VzDiffMargin", 0.2, "margin for +/- nSigma cut on vZ difference by FT0 and by tracks, cm"}; // o2-linter: disable=name/configurable (temporary fix) }; +// BC occupancy configurables +struct BcOccConfigurables : o2::framework::ConfigurableGroup { + std::string prefix = "bcoccOpts"; + o2::framework::Configurable amIneeded{"amIneeded", -1, "produce BC occupancy table aod::BcOccs or not. -1: automatic; 0: no; 1: yes"}; // o2-linter: disable=name/configurable (temporary fix) + o2::framework::Configurable bcsForOccupancy{"bcsForOccupancy", 0, "BCs for which the occupancy is calculated: 0 - all BCs, 1 - BCs with FT0, 2 - BCs with TVX, 3 - colliding BCs (others: -1)"}; +}; + +// dense BC occupancy configurables (all BCs of the TFs) +struct BcOccAllConfigurables : o2::framework::ConfigurableGroup { + std::string prefix = "bcoccAllOpts"; + o2::framework::Configurable amIneeded{"amIneeded", -1, "produce dense BC occupancy table aod::BcOccsAll or not. -1: automatic; 0: no; 1: yes"}; // o2-linter: disable=name/configurable (temporary fix) + o2::framework::Configurable bcsForOccupancy{"bcsForOccupancy", 0, "BCs for which the occupancy is calculated: 0 - all BCs, 1 - colliding BCs (others: -1)"}; +}; + // luminosity configurables struct LumiConfigurables : o2::framework::ConfigurableGroup { std::string prefix = "lumiOpts"; @@ -178,10 +192,12 @@ class BcSelectionModule bcselOpts = external_bcselopts; if (bcselOpts.amIneeded.value < 0) { - int bcSelNeeded = -1, evSelNeeded = -1; + int bcSelNeeded = -1, evSelNeeded = -1, bcOccNeeded = -1, bcOccAllNeeded = -1; bcselOpts.amIneeded.value = 0; o2::common::core::enableFlagIfTableRequired(context, "BcSels", bcSelNeeded); o2::common::core::enableFlagIfTableRequired(context, "EvSels", evSelNeeded); + o2::common::core::enableFlagIfTableRequired(context, "BcOccs", bcOccNeeded); + o2::common::core::enableFlagIfTableRequired(context, "BcOccsAll", bcOccAllNeeded); if (bcSelNeeded == 1) { bcselOpts.amIneeded.value = 1; LOGF(info, "BC Selection / Autodetection for aod::BcSels: subscription present, will generate."); @@ -190,7 +206,11 @@ class BcSelectionModule bcselOpts.amIneeded.value = 1; LOGF(info, "BC Selection / Autodetection for aod::BcSels: not there, but EvSel needed. Will generate."); } - if (bcSelNeeded == 0 && evSelNeeded == 0) { + if ((bcOccNeeded == 1 || bcOccAllNeeded == 1) && bcSelNeeded == 0 && evSelNeeded == 0) { + bcselOpts.amIneeded.value = 1; + LOGF(info, "BC Selection / Autodetection for aod::BcSels: not there, but BcOccs or BcOccsAll needed. Will generate."); + } + if (bcSelNeeded == 0 && evSelNeeded == 0 && bcOccNeeded == 0 && bcOccAllNeeded == 0) { LOGF(info, "BC Selection / Autodetection for aod::BcSels: not required. Skipping generation."); return; } @@ -645,8 +665,14 @@ class OccupancyCalculator bool noHighMultCollInPrevRof = false; // total FT0C amplitude in the previous ITS ROF below FT0CamplPerCollCutVetoOnCollInROF }; - // prepare n sources (one per collision, same order as in the collision table) with all fields reset - void resetSources(size_t n) { mSources.assign(n, Source{}); } + // prepare n sources (one per collision, same order as in the collision table) with all fields reset, + // and clear the list of TVX BCs (all per-DF inputs) + void resetSources(size_t n) + { + mSources.assign(n, Source{}); + mTVXBCsForTFBorderEstimate.clear(); + mIsReadyForBCQueries = false; + } // source i (for filling) Source& source(int32_t i) { return mSources[i]; } @@ -682,22 +708,50 @@ class OccupancyCalculator return wOccup; } + // colliding BCs with TVX and their FT0C amplitude, for the FT0C-based occupancy estimate near TF borders + // to be filled once per DF (after resetSources), in increasing order of global BC + void addTVXBC(int64_t globalBC, float ampFT0C) { mTVXBCsForTFBorderEstimate.push_back({globalBC, ampFT0C}); } // ampFT0C = -1 if there is no FT0 in this BC + // occupancy wrt mSources[iRef] (the source itself is excluded) - template - Result calculateForSource(int32_t iRef, std::map const& mapGlobalBcWithTVX, TFT0CofBC const& getFT0CofBC) + Result calculateForSource(int32_t iRef) { - return calculate(mSources[iRef].globalBC, mSources[iRef].vZ, iRef - 1, iRef + 1, mapGlobalBcWithTVX, getFT0CofBC); + return calculate(mSources, mSources[iRef].globalBC, mSources[iRef].vZ, iRef - 1, iRef + 1); } - // occupancy wrt refGlobalBC (refVz is used for the in-ROF veto on collisions with close vZ) - // sources are scanned backward starting from index iBefore and forward starting from index iAfter - // mapGlobalBcWithTVX: global BC -> BC index for colliding BCs with TVX, used for the FT0C-based estimate near TF borders - // getFT0CofBC(bcIndex, ampFT0C): returns false if there is no FT0 in this BC, otherwise sets ampFT0C - template - Result calculate(int64_t refGlobalBC, float refVz, int32_t iBefore, int32_t iAfter, std::map const& mapGlobalBcWithTVX, TFT0CofBC const& getFT0CofBC) + // prepare queries wrt BCs (numSourcesInBC, calculateForBC): copy of the sources sorted by global BC + // to be called after the sources are filled (done only once per DF) + void prepareBCQueries() + { + if (mIsReadyForBCQueries) { + return; + } + mSourcesSortedByBC = mSources; + std::stable_sort(mSourcesSortedByBC.begin(), mSourcesSortedByBC.end(), [](const Source& a, const Source& b) { return a.globalBC < b.globalBC; }); + mIsReadyForBCQueries = true; + } + + // number of sources (collisions) with a given global BC (requires prepareBCQueries) + int numSourcesInBC(int64_t globalBC) const + { + auto [iFirst, iEnd] = getSortedSourceRangeInBC(globalBC); + return iEnd - iFirst; + } + + // occupancy wrt a BC (requires prepareBCQueries): + // sources (collisions) in this BC are excluded, the in-ROF veto on collisions with close vZ is not applied (no vZ) + Result calculateForBC(int64_t globalBC) + { + auto [iFirst, iEnd] = getSortedSourceRangeInBC(globalBC); + return calculate(mSourcesSortedByBC, globalBC, std::numeric_limits::quiet_NaN(), iFirst - 1, iEnd); + } + + private: + // occupancy wrt refGlobalBC (refVz is used for the in-ROF veto on collisions with close vZ, NaN = veto not applied) + // srcs are scanned backward starting from index iBefore and forward starting from index iAfter + Result calculate(const std::vector& srcs, int64_t refGlobalBC, float refVz, int32_t iBefore, int32_t iAfter) { Result res; - const int32_t nSources = mSources.size(); + const int32_t nSources = srcs.size(); const int64_t tfId = getTFid(refGlobalBC); const int64_t rofId = getRofId(refGlobalBC); const int bcInTF = (refGlobalBC - mBcSOR) % mNBCsPerTF; @@ -719,16 +773,16 @@ class OccupancyCalculator }; // collisions in the same TF and ROF before and after the reference for (int32_t i = iBefore; i >= 0; i--) { - int64_t thisBC = mSources[i].globalBC; + int64_t thisBC = srcs[i].globalBC; if (getTFid(thisBC) != tfId || getRofId(thisBC) != rofId) break; - addCollInSameRof(mSources[i]); + addCollInSameRof(srcs[i]); } for (int32_t i = iAfter; i < nSources; i++) { - int64_t thisBC = mSources[i].globalBC; + int64_t thisBC = srcs[i].globalBC; if (getTFid(thisBC) != tfId || getRofId(thisBC) != rofId) break; - addCollInSameRof(mSources[i]); + addCollInSameRof(srcs[i]); } res.noCollInRofStrict = (nITS567tracksForSameRofVetoStrict == 0); res.noCollInRofStandard = (nCollsInRofWithFT0CAboveVetoStandard == 0); @@ -737,12 +791,12 @@ class OccupancyCalculator // ### occupancy in the previous ROF float totalFT0amplInPrevROF = 0; for (int32_t i = iBefore; i >= 0; i--) { - int64_t thisBC = mSources[i].globalBC; + int64_t thisBC = srcs[i].globalBC; if (getTFid(thisBC) != tfId) break; int64_t thisRofId = getRofId(thisBC); if (thisRofId == rofId - 1) - totalFT0amplInPrevROF += mSources[i].ampFT0C; + totalFT0amplInPrevROF += srcs[i].ampFT0C; else if (thisRofId < rofId - 1) break; } @@ -759,7 +813,7 @@ class OccupancyCalculator int proxyTotalMultInTimeWin = 0; mDeltaTimeMult.clear(); // (delta time in ns, proxy for N tracks) of collisions in the time window, for the median time auto addCollInTimeWin = [&](int32_t i, float dtNS) { - const Source& src = mSources[i]; + const Source& src = srcs[i]; mDeltaTimeMult.emplace_back(dtNS, src.proxyNtracks); proxyTotalMultInTimeWin = proxyTotalMultInTimeWin + static_cast(src.proxyNtracks); // int + float, as in the original std::accumulate over floats @@ -794,7 +848,7 @@ class OccupancyCalculator }; // collisions in the time window before and after the reference for (int32_t i = iBefore; i >= 0; i--) { - int64_t thisBC = mSources[i].globalBC; + int64_t thisBC = srcs[i].globalBC; if (getTFid(thisBC) != tfId) break; float dt = (thisBC - refGlobalBC) * bcNS; // ns @@ -803,7 +857,7 @@ class OccupancyCalculator addCollInTimeWin(i, dt); } for (int32_t i = iAfter; i < nSources; i++) { - int64_t thisBC = mSources[i].globalBC; + int64_t thisBC = srcs[i].globalBC; if (getTFid(thisBC) != tfId) break; float dt = (thisBC - refGlobalBC) * bcNS; // ns @@ -828,25 +882,29 @@ class OccupancyCalculator LOGP(debug, " --> median time = {}", res.medianTime); // if some collisions in the time window are close to the TF border - take FT0C amplitude instead of nTracks, using TVX BCs + // starting from the BC of the first such collision (only if it is a TVX BC itself) if (isFullInfoForOccupancy && canHaveAssocCollsWithinLastDriftTime && iFirstRejectedByTFborderCut >= 0) { - for (auto it = mapGlobalBcWithTVX.find(mSources[iFirstRejectedByTFborderCut].globalBC); it != mapGlobalBcWithTVX.end(); ++it) { - int64_t thisFoundGlobalBC = it->first; + const int64_t firstGlobalBC = srcs[iFirstRejectedByTFborderCut].globalBC; + auto it = std::lower_bound(mTVXBCsForTFBorderEstimate.begin(), mTVXBCsForTFBorderEstimate.end(), firstGlobalBC, [](const TVXBC& tvx, int64_t bc) { return tvx.globalBC < bc; }); + if (it != mTVXBCsForTFBorderEstimate.end() && it->globalBC != firstGlobalBC) { + it = mTVXBCsForTFBorderEstimate.end(); // exact match required + } + for (; it != mTVXBCsForTFBorderEstimate.end(); ++it) { + int64_t thisFoundGlobalBC = it->globalBC; if (getTFid(thisFoundGlobalBC) != tfId) break; float dt = (thisFoundGlobalBC - refGlobalBC) * bcNS; // ns if (dt > mTimeWinMaxNS) break; - float multT0C = -1; - if (getFT0CofBC(it->second, multT0C)) { + float multT0C = it->ampFT0C; + if (multT0C > 50.) // o2-linter: disable=magic-number (multiplicity threshold for occupancy calc) + { float wOccup = 1.; if (mUseWeightsForOccupancy) { wOccup = calcWeightForOccupancy(dt / 1e3); // ns -> us } - if (multT0C > 50.) // o2-linter: disable=magic-number (multiplicity threshold for occupancy calc) - { - nITS567tracksInFullTimeWindow += wOccup * multT0C / 10.; - sumAmpFT0CInFullTimeWindow += wOccup * multT0C; - } + nITS567tracksInFullTimeWindow += wOccup * multT0C / 10.; + sumAmpFT0CInFullTimeWindow += wOccup * multT0C; } } } @@ -866,8 +924,25 @@ class OccupancyCalculator return res; } - private: - std::vector mSources; // one entry per collision, same order as in the collision table + // index range [first, end) of the sources with a given global BC in mSourcesSortedByBC + std::pair getSortedSourceRangeInBC(int64_t globalBC) const + { + auto itFirst = std::lower_bound(mSourcesSortedByBC.begin(), mSourcesSortedByBC.end(), globalBC, [](const Source& src, int64_t bc) { return src.globalBC < bc; }); + auto itEnd = std::upper_bound(itFirst, mSourcesSortedByBC.end(), globalBC, [](int64_t bc, const Source& src) { return bc < src.globalBC; }); + return {static_cast(itFirst - mSourcesSortedByBC.begin()), static_cast(itEnd - mSourcesSortedByBC.begin())}; + } + + std::vector mSources; // one entry per collision, same order as in the collision table + std::vector mSourcesSortedByBC; // copy of mSources sorted by global BC, for queries wrt BCs + bool mIsReadyForBCQueries = false; // mSourcesSortedByBC is prepared for the current sources + + struct TVXBC { + int64_t globalBC = 0; // global BC of a colliding BC with TVX + float ampFT0C = -1; // FT0C amplitude in this BC, -1 if there is no FT0 + }; + // colliding BCs with TVX, also without a reconstructed collision, in increasing order of global BC: + // used near TF ends, where collisions are not reliably reconstructed and the occupancy is estimated from FT0C + std::vector mTVXBCsForTFBorderEstimate; int64_t mBcSOR = -1; // global bc of the start of the first orbit int64_t mNBCsPerTF = -1; // duration of TF in bcs @@ -970,6 +1045,10 @@ class EventSelectionModule OccupancyCalculator occupancyCalc; // occupancy estimators and flags (keeps its buffers between DFs) + o2::common::eventselection::BcOccConfigurables bcoccOpts; + o2::common::eventselection::BcOccAllConfigurables bcoccAllOpts; + bool isOccupancyInputReady = false; // occupancy inputs are filled for the current DF (by processRun3) + template void init(TContext& context, TEvSelOpts const& external_evselopts, THistoRegistry& histos, TMetadataInfo const& metadataInfo) { @@ -978,7 +1057,13 @@ class EventSelectionModule if (evselOpts.amIneeded.value < 0) { o2::common::core::enableFlagIfTableRequired(context, "EvSels", evselOpts.amIneeded.value); - if (evselOpts.amIneeded.value == 0) { + int bcOccNeeded = -1, bcOccAllNeeded = -1; + o2::common::core::enableFlagIfTableRequired(context, "BcOccs", bcOccNeeded); + o2::common::core::enableFlagIfTableRequired(context, "BcOccsAll", bcOccAllNeeded); + if (evselOpts.amIneeded.value == 0 && (bcOccNeeded == 1 || bcOccAllNeeded == 1)) { + evselOpts.amIneeded.value = 1; + LOGF(info, "Event Selection / Autodetecting for aod::EvSels: not required, but BcOccs or BcOccsAll needed. Will generate."); + } else if (evselOpts.amIneeded.value == 0) { LOGF(info, "Event Selection / Autodetecting for aod::EvSels: not required, won't generate."); return; } else { @@ -1003,6 +1088,36 @@ class EventSelectionModule histos.add("eventselection/hColCounterAcc", "", framework::kTH1D, {{1, 0., 1.}}); } + template + void initBcOccupancy(TContext& context, TBcOccOpts const& external_bcoccopts) + { + // read in configurations from the task where it's used + bcoccOpts = external_bcoccopts; + if (bcoccOpts.amIneeded.value < 0) { + o2::common::core::enableFlagIfTableRequired(context, "BcOccs", bcoccOpts.amIneeded.value); + if (bcoccOpts.amIneeded.value == 0) { + LOGF(info, "BC occupancy / Autodetecting for aod::BcOccs: not required, won't generate."); + } else { + LOGF(info, "BC occupancy / Autodetecting for aod::BcOccs: subscription present, will generate."); + } + } + } + + template + void initBcOccupancyAll(TContext& context, TBcOccAllOpts const& external_bcoccallopts) + { + // read in configurations from the task where it's used + bcoccAllOpts = external_bcoccallopts; + if (bcoccAllOpts.amIneeded.value < 0) { + o2::common::core::enableFlagIfTableRequired(context, "BcOccsAll", bcoccAllOpts.amIneeded.value); + if (bcoccAllOpts.amIneeded.value == 0) { + LOGF(info, "BC occupancy (all BCs) / Autodetecting for aod::BcOccsAll: not required, won't generate."); + } else { + LOGF(info, "BC occupancy (all BCs) / Autodetecting for aod::BcOccsAll: subscription present, will generate."); + } + } + } + //__________________________________________________ template bool configure(TCCDB& ccdb, TTimestamps const& timestamps, TBCs const& bcs) @@ -1161,6 +1276,7 @@ class EventSelectionModule template void processRun3(TCCDB const& ccdb, THistoRegistry& histos, TBCs const& bcs, TCollisions const& cols, TPVTracks const& pvTracks, TFT0s const& ft0s, TSlicecache& cache, TTimestamps const& timestamps, TBcSelBuffer const& bcselbuffer, TEvselCursor& evsel) { + isOccupancyInputReady = false; if (evselOpts.amIneeded.value == 0) { return; // dummy process } @@ -1206,7 +1322,6 @@ class EventSelectionModule } return; } - occupancyCalc.resetSources(cols.size()); // per-collision inputs for the occupancy calculation std::vector vIsVertexITSTPC(cols.size(), 0); // at least one of vertex contributors is ITS-TPC track std::vector vIsVertexTOFmatched(cols.size(), 0); // at least one of vertex contributors is matched to TOF std::vector vIsVertexTRDmatched(cols.size(), 0); // at least one of vertex contributors is matched to TRD @@ -1222,6 +1337,7 @@ class EventSelectionModule std::vector vIsVertexTPC(cols.size(), 0); std::vector vIsVertexHighPtTPC(cols.size(), 0); std::vector vNcontributors(cols.size(), 0); + std::vector vNumTracksITS567(cols.size(), 0); std::vector vWeightedTimesTPCnoTOFnoTRD(cols.size(), 0); std::vector vWeightedSigmaTPCnoTOFnoTRD(cols.size(), 0); @@ -1232,8 +1348,6 @@ class EventSelectionModule // first loop to match collisions to TVX, also extract other per-collision information for further use for (const auto& col : cols) { int32_t colIndex = col.globalIndex(); - occupancyCalc.source(colIndex).vZ = col.posZ(); - vTrackTimesTOF.clear(); vTrackTimesTRDnoTOF.clear(); int nPvTracksTPCnoTOFnoTRD = 0; @@ -1243,7 +1357,7 @@ class EventSelectionModule for (const auto& track : colPvTracks) { float trackTime = track.trackTime(); if (track.itsNCls() >= 5) // o2-linter: disable=magic-number (number of ITS clusters >=5) - occupancyCalc.source(colIndex).nTracksITS567++; + vNumTracksITS567[colIndex]++; if (track.hasTRD()) vIsVertexTRDmatched[colIndex] = 1; if (track.hasTPC()) @@ -1404,10 +1518,13 @@ class EventSelectionModule } } - // fill remaining per-collision inputs for the occupancy calculation + // fill per-collision inputs for the occupancy calculation + occupancyCalc.resetSources(cols.size()); // per-collision inputs for the occupancy calculation for (const auto& col : cols) { int32_t colIndex = col.globalIndex(); OccupancyCalculator::Source& occupSrc = occupancyCalc.source(colIndex); + occupSrc.vZ = col.posZ(); + occupSrc.nTracksITS567 = vNumTracksITS567[colIndex]; occupSrc.globalBC = vFoundGlobalBC[colIndex]; const auto& bcselEntry = bcselbuffer[vFoundBCindex[colIndex]]; if (bcselEntry.foundFT0Id > -1) { @@ -1420,17 +1537,13 @@ class EventSelectionModule occupSrc.proxyNtracks = BITCHECK64(bcselEntry.selection, aod::evsel::kNoITSROFrameBorder) ? occupSrc.nTracksITS567 : occupSrc.ampFT0C / 10.; occupSrc.isRejectedByTFborderCut = !BITCHECK64(bcselEntry.selection, aod::evsel::kNoTimeFrameBorder); } - occupancyCalc.configure(bcSOR, nBCsPerTF, rofOffset, rofLength, evselOpts); - - // FT0C amplitude in a given BC, for the occupancy estimation near TF borders - auto getFT0CofBC = [&bcs](int32_t bcIndex, float& ampFT0C) { + // colliding TVX BCs with their FT0C amplitude, for the occupancy estimation near TF borders + for (const auto& [globalBC, bcIndex] : mapGlobalBcWithTVX) { auto bc = bcs.iteratorAt(bcIndex); - if (!bc.has_ft0()) { - return false; - } - ampFT0C = bc.ft0().sumAmpC(); - return true; - }; + occupancyCalc.addTVXBC(globalBC, bc.has_ft0() ? bc.ft0().sumAmpC() : -1.f); + } + occupancyCalc.configure(bcSOR, nBCsPerTF, rofOffset, rofLength, evselOpts); + isOccupancyInputReady = true; for (const auto& col : cols) { int32_t colIndex = col.globalIndex(); @@ -1443,7 +1556,7 @@ class EventSelectionModule int32_t foundZDC = bcselEntry.foundZDCId; // occupancy estimators and flags wrt this collision - const OccupancyCalculator::Result occ = occupancyCalc.calculateForSource(colIndex, mapGlobalBcWithTVX, getFT0CofBC); + const OccupancyCalculator::Result occ = occupancyCalc.calculateForSource(colIndex); // compare zVtx from FT0 and from PV bool isGoodZvtxFT0vsPV = 0; @@ -1520,6 +1633,112 @@ class EventSelectionModule occ.nTracksITS567inTimeWin, occ.sumAmpFT0CinTimeWin, occ.medianTime); } } // end processRun3 + + //__________________________________________________ + // occupancy wrt BCs (aod::BcOccs), to be called after processRun3 for the same DF (uses the occupancy inputs filled there) + // one entry per BC; for BCs without calculated occupancy: occupancies -1, median time 0, flags 0 + template + void processRun3BcOccupancy(TBCs const& bcs, TBcSelBuffer const& bcselbuffer, TBcOccCursor& bcocc) + { + if (bcoccOpts.amIneeded.value == 0) { + return; + } + if (isOccupancyInputReady) { + occupancyCalc.prepareBCQueries(); + } else { + LOGP(warning, "BC occupancy: occupancy inputs not available for this DF, filling aod::BcOccs with default values"); + } + + const int bcsForOccupancy = bcoccOpts.bcsForOccupancy.value; + for (const auto& bc : bcs) { + if (!isOccupancyInputReady) { + bcocc(-1, -1.f, 0.f, 0, 0); + continue; + } + int64_t globalBC = bc.globalBC(); + const auto nCollsInBC = static_cast(std::min(occupancyCalc.numSourcesInBC(globalBC), static_cast(std::numeric_limits::max()))); + + // BCs for which the occupancy is calculated + bool isSelected = true; + const auto& bcselEntry = bcselbuffer[bc.globalIndex()]; + if (bcsForOccupancy == 1) { // o2-linter: disable=magic-number (BCs with FT0) + isSelected = bcselEntry.foundFT0Id > -1; + } else if (bcsForOccupancy == 2) { // o2-linter: disable=magic-number (BCs with TVX) + isSelected = BITCHECK64(bcselEntry.selection, aod::evsel::kIsTriggerTVX); + } else if (bcsForOccupancy == 3) { // o2-linter: disable=magic-number (colliding BCs) + isSelected = bcPatternB[globalBC % nBCsPerOrbit]; + } + if (!isSelected) { + bcocc(-1, -1.f, 0.f, 0, nCollsInBC); + continue; + } + + const OccupancyCalculator::Result occ = occupancyCalc.calculateForBC(globalBC); + bcocc(occ.nTracksITS567inTimeWin, occ.sumAmpFT0CinTimeWin, occ.medianTime, getBcOccupancyFlags(occ), nCollsInBC); + } + } // end processRun3BcOccupancy + + //__________________________________________________ + // occupancy wrt every BC of the TFs present in the DF (aod::BcOccsAll, not joinable), to be called after processRun3 for the same DF + // rows: for each TF (in increasing order) all its BCs, in increasing order of global BC + // for BCs without calculated occupancy: occupancies -1, median time 0, flags 0; no rows if the occupancy inputs are not available + template + void processRun3BcOccupancyAll(TBCs const& bcs, TBcOccAllCursor& bcoccall) + { + if (bcoccAllOpts.amIneeded.value == 0) { + return; + } + if (!isOccupancyInputReady || nBCsPerTF <= 0) { + LOGP(warning, "BC occupancy (all BCs): occupancy inputs not available for this DF, no entries in aod::BcOccsAll"); + return; + } + occupancyCalc.prepareBCQueries(); + + // TFs present in this DF (from the BC table): needed to write all BCs of each TF, since the BC table + // does not cover the TF borders, and the TFs in a DF are not necessarily consecutive (no rows for missing TFs) + std::vector tfIds; + for (const auto& bc : bcs) { + int64_t globalBC = bc.globalBC(); + if (globalBC < bcSOR) { + continue; + } + int64_t tfId = (globalBC - bcSOR) / nBCsPerTF; + if (tfIds.empty() || tfIds.back() != tfId) { + tfIds.push_back(tfId); + } + } + std::sort(tfIds.begin(), tfIds.end()); + tfIds.erase(std::unique(tfIds.begin(), tfIds.end()), tfIds.end()); + + bcoccall.reserve(static_cast(tfIds.size()) * nBCsPerTF); + const bool collidingBCsOnly = (bcoccAllOpts.bcsForOccupancy.value == 1); + for (const auto& tfId : tfIds) { + const int64_t tfStartBC = bcSOR + tfId * nBCsPerTF; + for (int64_t globalBC = tfStartBC; globalBC < tfStartBC + nBCsPerTF; globalBC++) { + const auto nCollsInBC = static_cast(std::min(occupancyCalc.numSourcesInBC(globalBC), static_cast(std::numeric_limits::max()))); + if (collidingBCsOnly && !bcPatternB[globalBC % nBCsPerOrbit]) { + bcoccall(static_cast(globalBC), -1, -1.f, 0.f, 0, nCollsInBC); + continue; + } + const OccupancyCalculator::Result occ = occupancyCalc.calculateForBC(globalBC); + bcoccall(static_cast(globalBC), occ.nTracksITS567inTimeWin, occ.sumAmpFT0CinTimeWin, occ.medianTime, getBcOccupancyFlags(occ), nCollsInBC); + } + } + } // end processRun3BcOccupancyAll + + // flags of aod::BcOccs and aod::BcOccsAll (see aod::bcocc::BcOccupancyFlags) + static uint8_t getBcOccupancyFlags(const OccupancyCalculator::Result& occ) + { + // note: BcOccupancyFlags must fit in the 8-bit bitmap + uint8_t flags = 0; + flags |= occ.noCollInTimeRangeNarrow ? BIT(aod::bcocc::kNoCollInTimeRangeNarrow) : 0; + flags |= occ.noCollInTimeRangeStrict ? BIT(aod::bcocc::kNoCollInTimeRangeStrict) : 0; + flags |= occ.noCollInTimeRangeStandard ? BIT(aod::bcocc::kNoCollInTimeRangeStandard) : 0; + flags |= occ.noCollInRofStrict ? BIT(aod::bcocc::kNoCollInRofStrict) : 0; + flags |= occ.noCollInRofStandard && occ.noCollInRofWithCloseVz ? BIT(aod::bcocc::kNoCollInRofStandard) : 0; // close-vZ part is not applied for BCs + flags |= occ.noHighMultCollInPrevRof ? BIT(aod::bcocc::kNoHighMultCollInPrevRof) : 0; + return flags; + } }; // end EventSelectionModule class LumiModule From f1a5bff3a10820f8b6559d5ed4c7a75d04291fc0 Mon Sep 17 00:00:00 2001 From: Igor Altsybeev Date: Wed, 11 Nov 2026 11:18:24 +0100 Subject: [PATCH 02/14] add occupancy-related structures and enums to EventSelection --- Common/DataModel/EventSelection.h | 105 ++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/Common/DataModel/EventSelection.h b/Common/DataModel/EventSelection.h index 7000f5dc69e..e1636de2c59 100644 --- a/Common/DataModel/EventSelection.h +++ b/Common/DataModel/EventSelection.h @@ -18,6 +18,12 @@ #define COMMON_DATAMODEL_EVENTSELECTION_H_ #include +#include + +#include +#include +#include +#include namespace o2::aod { @@ -85,6 +91,105 @@ DECLARE_SOA_TABLE(EvSels, "AOD", "EVSEL", //! evsel::SumAmpFT0CInTimeRange, evsel::OccupancyMedianTime); using EvSel = EvSels::iterator; + +// occupancy wrt BCs: same estimators and flags as in EvSels (occupancy from other collisions nearby in time), +// calculated wrt a BC; collisions found in this BC are excluded (their number is stored in numCollsInBC) +// N.B.: not related to the occupancy tables in OccupancyTables.h +namespace bcocc +{ +enum BcOccupancyFlags { // bits of occupancyFlags + kNoCollInTimeRangeNarrow = 0, // no other collisions in specified time range (narrower than Strict) + kNoCollInTimeRangeStrict, // no other collisions in specified time range + kNoCollInTimeRangeStandard, // no other collisions in specified time range with per-collision multiplicity above threshold + kNoCollInRofStrict, // no other collisions in this ITS Readout Frame + kNoCollInRofStandard, // no other collisions in this ITS Readout Frame with per-collision multiplicity above threshold (no veto on collisions with close vZ) + kNoHighMultCollInPrevRof // FT0C amplitude in previous ITS ROF is below threshold +}; +DECLARE_SOA_COLUMN(NumTracksInTimeRange, trackOccupancyInTimeRange, int); //! Occupancy in specified time interval by a number of tracks from nearby collisions (-1 if not available) // o2-linter: disable=name/o2-column +DECLARE_SOA_COLUMN(SumAmpFT0CInTimeRange, ft0cOccupancyInTimeRange, float); //! Occupancy in specified time interval by a sum of FT0C amplitudes from nearby collisions (-1 if not available) // o2-linter: disable=name/o2-column +DECLARE_SOA_COLUMN(OccupancyMedianTime, occupancyMedianTime, float); //! Median time for the occupancy in specified time interval (the delta-time wrt to the BC at which occupancy crosses 1/2 of its full value) +DECLARE_SOA_BITMAP_COLUMN(OccupancyFlags, occupancyFlags, 8); //! Bitmask of occupancy flags (see BcOccupancyFlags), check with occupancyFlags_bit(bit) +DECLARE_SOA_COLUMN(NumCollsInBC, numCollsInBC, int16_t); //! Number of collisions found in this BC (excluded from the occupancy) +} // namespace bcocc + +// bc-joinable occupancy +DECLARE_SOA_TABLE(BcOccs, "AOD", "BCOCC", //! + bcocc::NumTracksInTimeRange, + bcocc::SumAmpFT0CInTimeRange, + bcocc::OccupancyMedianTime, + bcocc::OccupancyFlags, + bcocc::NumCollsInBC); +using BcOcc = BcOccs::iterator; + +// occupancy wrt every BC of the TFs present in the DF (not joinable): for each TF (in increasing order) all its BCs, in increasing order of global BC +// same estimators and flags as in BcOccs +DECLARE_SOA_TABLE(BcOccsAll, "AOD", "BCOCCALL", //! + bc::GlobalBC, + bcocc::NumTracksInTimeRange, + bcocc::SumAmpFT0CInTimeRange, + bcocc::OccupancyMedianTime, + bcocc::OccupancyFlags, + bcocc::NumCollsInBC); +using BcOccAll = BcOccsAll::iterator; + +namespace bcocc +{ +// fast lookup of the aod::BcOccsAll entry for a given global BC +// usage (within the process function, the index keeps a pointer to the table): +// aod::bcocc::BcOccAllIndex bcOccAllIndexed(bcOccsAll); +// auto occ = bcOccAllIndexed[bc.globalBC()]; // iterator of aod::BcOccsAll +// int occByTracks = occ.trackOccupancyInTimeRange(); +template +class BcOccAllIndex +{ + public: + explicit BcOccAllIndex(TBcOccsAll const& bcOccsAll) : mTable(&bcOccsAll) + { + const int64_t nRows = static_cast(bcOccsAll.size()); + auto offsetAt = [&bcOccsAll](int64_t row) { return static_cast(bcOccsAll.iteratorAt(row).globalBC()) - row; }; + for (int64_t first = 0, end = 0; first < nRows; first = end) { + // the offset is non-decreasing (global BCs are strictly increasing): the segment ends at the first row with a larger offset + const int64_t offset = offsetAt(first); + end = *std::ranges::partition_point(std::views::iota(first, nRows), [&](int64_t row) { return offsetAt(row) == offset; }); + mSegments.push_back({offset + first, offset + end, offset}); + } + } + + // entry of aod::BcOccsAll for this global BC (fatal if the BC is not in the table) + auto operator[](uint64_t globalBC) const + { + const int64_t row = rowOf(globalBC); + if (row < 0) { + LOGP(fatal, "BcOccAllIndex: global BC {} is not in aod::BcOccsAll ({} rows): its TF is not in this DF, or the occupancy is not available for this DF", globalBC, mTable->size()); + } + return mTable->iteratorAt(row); + } + + // helper function to check if a global BC is contained in the table + bool contains(uint64_t globalBC) const { return rowOf(globalBC) >= 0; } + + private: + // row of aod::BcOccsAll for this global BC, -1 if the BC is not in the table + int64_t rowOf(uint64_t globalBC) const + { + const auto bc = static_cast(globalBC); + for (const auto& seg : mSegments) { // a few segments: one per range of consecutive TFs in the DF + if (bc >= seg.firstGlobalBC && bc < seg.endGlobalBC) { + return bc - seg.offset; + } + } + return -1; + } + + struct Segment { + int64_t firstGlobalBC; // first global BC of the segment + int64_t endGlobalBC; // last global BC of the segment + 1 + int64_t offset; // globalBC - row, constant within the segment + }; + TBcOccsAll const* mTable; // not owned: the index is valid as long as the table (within the process function) + std::vector mSegments; // in increasing order of global BC +}; +} // namespace bcocc } // namespace o2::aod #endif // COMMON_DATAMODEL_EVENTSELECTION_H_ From b7d1b3a56accb7caa2244b0f5e238d3d613a0577 Mon Sep 17 00:00:00 2001 From: Igor Altsybeev Date: Wed, 11 Nov 2026 11:20:51 +0100 Subject: [PATCH 03/14] add bc occupancy QA task --- DPG/Tasks/AOTEvent/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/DPG/Tasks/AOTEvent/CMakeLists.txt b/DPG/Tasks/AOTEvent/CMakeLists.txt index 202543fa7c2..a7e69449b34 100644 --- a/DPG/Tasks/AOTEvent/CMakeLists.txt +++ b/DPG/Tasks/AOTEvent/CMakeLists.txt @@ -53,3 +53,8 @@ o2physics_add_dpl_workflow(occupancy-vs-dedx-qa SOURCES dEdxVsOccupancyWithTrackQAinfo.cxx PUBLIC_LINK_LIBRARIES O2::Framework O2Physics::AnalysisCore O2Physics::AnalysisCCDB O2::DetectorsBase COMPONENT_NAME Analysis) + +o2physics_add_dpl_workflow(bc-occupancy-qa + SOURCES bcOccupancyQa.cxx + PUBLIC_LINK_LIBRARIES O2::Framework O2Physics::AnalysisCore + COMPONENT_NAME Analysis) From 99853eae430e7dc689353abba6285ba8794dc6ca Mon Sep 17 00:00:00 2001 From: Igor Altsybeev Date: Wed, 11 Nov 2026 11:22:10 +0100 Subject: [PATCH 04/14] add BcOccupancy and BcOccupancyAll modules --- Common/TableProducer/eventSelectionService.cxx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Common/TableProducer/eventSelectionService.cxx b/Common/TableProducer/eventSelectionService.cxx index 4be5b73b5a5..603e9fd402f 100644 --- a/Common/TableProducer/eventSelectionService.cxx +++ b/Common/TableProducer/eventSelectionService.cxx @@ -112,6 +112,8 @@ struct EventselectionRun3 { // o2-linter: disable=name/workflow-file (exception o2::common::eventselection::EvselConfigurables evselOpts; o2::common::eventselection::EventSelectionModule evselmodule; + o2::common::eventselection::BcOccConfigurables bcoccOpts; + o2::common::eventselection::BcOccAllConfigurables bcoccAllOpts; o2::common::eventselection::LumiConfigurables lumiOpts; o2::common::eventselection::LumiModule lumimodule; @@ -119,6 +121,8 @@ struct EventselectionRun3 { // o2-linter: disable=name/workflow-file (exception Produces timestampTable; /// Table with SOR timestamps produced by the task Produces bcsel; Produces evsel; + Produces bcocc; + Produces bcoccall; // for slicing SliceCache cache; @@ -149,6 +153,8 @@ struct EventselectionRun3 { // o2-linter: disable=name/workflow-file (exception timestampMod.init(timestampConfigurables, metadataInfo); bcselmodule.init(context, bcselOpts, histos, metadataInfo); evselmodule.init(context, evselOpts, histos, metadataInfo); + evselmodule.initBcOccupancy(context, bcoccOpts); + evselmodule.initBcOccupancyAll(context, bcoccAllOpts); lumimodule.init(context, lumiOpts, histos); } @@ -163,6 +169,8 @@ struct EventselectionRun3 { // o2-linter: disable=name/workflow-file (exception timestampMod.process(bcs, ccdb, timestamps, timestampTable); bcselmodule.processRun3(ccdb, histos, bcs, timestamps, bcselsbuffer, bcsel); evselmodule.processRun3(ccdb, histos, bcs, collisions, pvTracks, ft0s, cache, timestamps, bcselsbuffer, evsel); + evselmodule.processRun3BcOccupancy(bcs, bcselsbuffer, bcocc); + evselmodule.processRun3BcOccupancyAll(bcs, bcoccall); lumimodule.process(ccdb, histos, bcs, timestamps, bcselsbuffer); } }; From b9c3aea17886fb91be3fc8fb2d7e51a96d9b22e4 Mon Sep 17 00:00:00 2001 From: Igor Altsybeev Date: Wed, 11 Nov 2026 11:22:59 +0100 Subject: [PATCH 05/14] Add files via upload --- DPG/Tasks/AOTEvent/bcOccupancyQa.cxx | 185 +++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 DPG/Tasks/AOTEvent/bcOccupancyQa.cxx diff --git a/DPG/Tasks/AOTEvent/bcOccupancyQa.cxx b/DPG/Tasks/AOTEvent/bcOccupancyQa.cxx new file mode 100644 index 00000000000..f1a6f017dae --- /dev/null +++ b/DPG/Tasks/AOTEvent/bcOccupancyQa.cxx @@ -0,0 +1,185 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file bcOccupancyQa.cxx +/// \brief QA of the occupancy wrt bunch crossings: aod::BcOccsAll (all BCs of the TFs), produced by the event selection service +/// +/// \author Igor Altsybeev + +#include "Common/DataModel/EventSelection.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace o2; +using namespace o2::framework; + +struct BcOccupancyQaTask { + Configurable nBinsOccupancyByTracks{"nBinsOccupancyByTracks", 500, "number of bins for the occupancy by ITS tracks"}; + Configurable maxOccupancyByTracks{"maxOccupancyByTracks", 25000., "upper edge of the axis for the occupancy by ITS tracks"}; + Configurable nBinsOccupancyByFT0C{"nBinsOccupancyByFT0C", 500, "number of bins for the occupancy by FT0C amplitudes"}; + Configurable maxOccupancyByFT0C{"maxOccupancyByFT0C", 250000., "upper edge of the axis for the occupancy by FT0C amplitudes"}; + Configurable nBinsBcInTF{"nBinsBcInTF", 1188, "number of bins for the BC position in the TF"}; + Configurable nOrbitsPerTF{"nOrbitsPerTF", 32, "number of orbits per TF, for the histograms vs BC position in the TF (128 in 2022)"}; + + HistogramRegistry histos{"histos", {}, OutputObjHandlingPolicy::AnalysisObject}; + + static constexpr int NFlags = 6; + static constexpr std::array FlagBits = {aod::bcocc::kNoCollInTimeRangeNarrow, aod::bcocc::kNoCollInTimeRangeStrict, aod::bcocc::kNoCollInTimeRangeStandard, + aod::bcocc::kNoCollInRofStrict, aod::bcocc::kNoCollInRofStandard, aod::bcocc::kNoHighMultCollInPrevRof}; + static constexpr std::array FlagNames = {"NoCollInTimeRangeNarrow", "NoCollInTimeRangeStrict", "NoCollInTimeRangeStandard", + "NoCollInRofStrict", "NoCollInRofStandard", "NoHighMultCollInPrevRof"}; + static constexpr int NBCsPerOrbit = 3564; + int64_t nBCsPerTF = 0; + + void init(InitContext&) + { + nBCsPerTF = static_cast(nOrbitsPerTF.value) * NBCsPerOrbit; + const AxisSpec axisOccupancyByTracks{nBinsOccupancyByTracks.value, 0., maxOccupancyByTracks.value, "occupancy by ITS tracks"}; + const AxisSpec axisOccupancyByFT0C{nBinsOccupancyByFT0C.value, 0., maxOccupancyByFT0C.value, "occupancy by FT0C amplitudes"}; + const AxisSpec axisMedianTime{280, -40., 100., "median time of the occupancy (#mus)"}; + const AxisSpec axisNumCollsInBC{11, -0.5, 10.5, "number of collisions found in the BC"}; + const AxisSpec axisBcInTF{nBinsBcInTF.value, 0., static_cast(nBCsPerTF), "BC in TF"}; + const AxisSpec axisBcInOrbit{NBCsPerOrbit, -0.5, NBCsPerOrbit - 0.5, "BC in orbit"}; + const AxisSpec axisFlags{NFlags + 1, -0.5, NFlags + 0.5, ""}; + const AxisSpec axisBcCounter{3, -0.5, 2.5, ""}; + const AxisSpec axisIndexCheck{6, -0.5, 5.5, ""}; + + // all BCs + histos.add("hBcCounter", "number of BCs", kTH1D, {axisBcCounter}); + histos.add("hNumCollsInBC", "all BCs", kTH1D, {axisNumCollsInBC}); + histos.add("hBcInTF", "all BCs", kTH1D, {axisBcInTF}); + // BCs with calculated occupancy + histos.add("hBcInTFOccupancyDefined", "BCs with calculated occupancy", kTH1D, {axisBcInTF}); + histos.add("hOccupancyByTracks", "BCs with calculated occupancy", kTH1D, {axisOccupancyByTracks}); + histos.add("hOccupancyByFT0C", "BCs with calculated occupancy", kTH1D, {axisOccupancyByFT0C}); + histos.add("hOccupancyByTracksVsFT0C", "BCs with calculated occupancy", kTH2D, {axisOccupancyByFT0C, axisOccupancyByTracks}); + histos.add("hMedianTime", "BCs with calculated occupancy", kTH1D, {axisMedianTime}); + histos.add("pOccupancyByTracksVsBcInTF", "BCs with calculated occupancy;BC in TF;mean occupancy by ITS tracks", kTProfile, {axisBcInTF}); + histos.add("pOccupancyByTracksVsBcInOrbit", "BCs with calculated occupancy;BC in orbit;mean occupancy by ITS tracks", kTProfile, {axisBcInOrbit}); + // occupancy flags: first bin - all BCs with calculated occupancy, then the BCs with a given flag set + histos.add("hOccupancyFlags", "BCs with calculated occupancy", kTH1D, {axisFlags}); + histos.add("hOccupancyFlagsVsOccupancyByTracks", "BCs with calculated occupancy", kTH2D, {axisFlags, axisOccupancyByTracks}); + // cross-check of the lookup by global BC (aod::bcocc::BcOccAllIndex) + histos.add("hBcIndexCheck", "lookup in BcOccsAll by global BC", kTH1D, {axisIndexCheck}); + + auto hBcCounter = histos.get(HIST("hBcCounter")); + hBcCounter->GetXaxis()->SetBinLabel(1, "all"); + hBcCounter->GetXaxis()->SetBinLabel(2, "occupancy calculated"); + hBcCounter->GetXaxis()->SetBinLabel(3, "occupancy not available"); + auto hFlags = histos.get(HIST("hOccupancyFlags")); + auto hFlagsVsOcc = histos.get(HIST("hOccupancyFlagsVsOccupancyByTracks")); + hFlags->GetXaxis()->SetBinLabel(1, "all"); + hFlagsVsOcc->GetXaxis()->SetBinLabel(1, "all"); + for (int i = 0; i < NFlags; i++) { + hFlags->GetXaxis()->SetBinLabel(i + 2, FlagNames[i]); + hFlagsVsOcc->GetXaxis()->SetBinLabel(i + 2, FlagNames[i]); + } + auto hBcIndexCheck = histos.get(HIST("hBcIndexCheck")); + hBcIndexCheck->GetXaxis()->SetBinLabel(1, "BcOccsAll rows"); + hBcIndexCheck->GetXaxis()->SetBinLabel(2, "row found by its global BC"); + hBcIndexCheck->GetXaxis()->SetBinLabel(3, "BCs in aod::BCs"); + hBcIndexCheck->GetXaxis()->SetBinLabel(4, "BC found in BcOccsAll"); + hBcIndexCheck->GetXaxis()->SetBinLabel(5, "BC found, same global BC"); + hBcIndexCheck->GetXaxis()->SetBinLabel(6, "BC not found"); + } + + void process(aod::BcOccsAll const& bcOccs, aod::BCs const& bcs) + { + // cross-check of the lookup by global BC: every BC of the BC table should be found, with the same global BC + // (not found: BCs before the start of run, or no BcOccsAll entries for this DF) + aod::bcocc::BcOccAllIndex bcOccAllIndexed(bcOccs); + for (const auto& bc : bcs) { + histos.fill(HIST("hBcIndexCheck"), 2); + if (!bcOccAllIndexed.contains(bc.globalBC())) { + histos.fill(HIST("hBcIndexCheck"), 5); + continue; + } + histos.fill(HIST("hBcIndexCheck"), 3); + if (bcOccAllIndexed[bc.globalBC()].globalBC() == bc.globalBC()) { + histos.fill(HIST("hBcIndexCheck"), 4); + } + } + + if (bcOccs.size() == 0) { + return; + } + // BC position in the TF: the table contains complete TFs and TF starts differ by multiples of nBCsPerTF, + // so bcInTF = (globalBC - globalBC of the first row) % nBCsPerTF; check that nOrbitsPerTF is consistent with the table + const uint64_t firstGlobalBC = bcOccs.iteratorAt(0).globalBC(); + const uint64_t lastGlobalBC = bcOccs.iteratorAt(bcOccs.size() - 1).globalBC(); + const bool isBcInTFValid = (static_cast(bcOccs.size()) % nBCsPerTF == 0) && (static_cast((lastGlobalBC - firstGlobalBC) % nBCsPerTF) == nBCsPerTF - 1); + if (!isBcInTFValid) { + LOGP(warning, "BC occupancy QA: table size {} is not consistent with {} orbits per TF, no histograms vs BC in TF for this DF", bcOccs.size(), nOrbitsPerTF.value); + } + + for (const auto& bcOcc : bcOccs) { + const uint64_t globalBC = bcOcc.globalBC(); + const int bcInTF = isBcInTFValid ? static_cast((globalBC - firstGlobalBC) % nBCsPerTF) : -1; + const int bcInOrbit = static_cast(globalBC % NBCsPerOrbit); + histos.fill(HIST("hBcCounter"), 0); + histos.fill(HIST("hNumCollsInBC"), bcOcc.numCollsInBC()); + histos.fill(HIST("hBcIndexCheck"), 0); + if (bcOccAllIndexed.contains(globalBC) && bcOccAllIndexed[globalBC].globalBC() == globalBC) { // global BCs are unique: same row + histos.fill(HIST("hBcIndexCheck"), 1); + } + if (isBcInTFValid) { + histos.fill(HIST("hBcInTF"), bcInTF); + } + + // occupancy is not available (BC is close to TF borders, or not selected for the calculation) + const int occByTracks = bcOcc.trackOccupancyInTimeRange(); + const float occByFT0C = bcOcc.ft0cOccupancyInTimeRange(); + if (occByTracks < 0 || occByFT0C < 0) { + histos.fill(HIST("hBcCounter"), 2); + continue; + } + histos.fill(HIST("hBcCounter"), 1); + histos.fill(HIST("hOccupancyByTracks"), occByTracks); + histos.fill(HIST("hOccupancyByFT0C"), occByFT0C); + histos.fill(HIST("hOccupancyByTracksVsFT0C"), occByFT0C, occByTracks); + histos.fill(HIST("hMedianTime"), bcOcc.occupancyMedianTime()); + histos.fill(HIST("pOccupancyByTracksVsBcInOrbit"), bcInOrbit, occByTracks); + if (isBcInTFValid) { + histos.fill(HIST("hBcInTFOccupancyDefined"), bcInTF); + histos.fill(HIST("pOccupancyByTracksVsBcInTF"), bcInTF, occByTracks); + } + + histos.fill(HIST("hOccupancyFlags"), 0); + histos.fill(HIST("hOccupancyFlagsVsOccupancyByTracks"), 0, occByTracks); + for (int i = 0; i < NFlags; i++) { + if (bcOcc.occupancyFlags_bit(FlagBits[i])) { + histos.fill(HIST("hOccupancyFlags"), i + 1); + histos.fill(HIST("hOccupancyFlagsVsOccupancyByTracks"), i + 1, occByTracks); + } + } + } + } +}; + +WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) +{ + return WorkflowSpec{ + adaptAnalysisTask(cfgc)}; +} From f4293b57403d747e07071b9745b27091d16096b8 Mon Sep 17 00:00:00 2001 From: Igor Altsybeev Date: Wed, 11 Nov 2026 11:26:01 +0100 Subject: [PATCH 06/14] add plotBcOccupancyQa macro for BC occupancy QA --- DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C | 272 ++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C diff --git a/DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C b/DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C new file mode 100644 index 00000000000..48f70c28a01 --- /dev/null +++ b/DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C @@ -0,0 +1,272 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file plotBcOccupancyQa.C +/// \brief Plots and summary of the BC occupancy QA (output of o2-analysis-bc-occupancy-qa) +/// +/// Usage: +/// root -l -b -q 'plotBcOccupancyQa.C("AnalysisResults.root")' +/// root -l -b -q 'plotBcOccupancyQa.C("AnalysisResults.root", "bcOccupancyQa.pdf", "bc-occupancy-qa-task")' +/// +/// \author Igor Altsybeev + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace bcoccqa +{ +TDirectory* gDir = nullptr; + +// histogram from the task directory (or from one of its subdirectories), detached from the file +template +T* getHist(const char* name) +{ + TObject* obj = gDir->Get(name); + if (!obj) { + for (TObject* key : *gDir->GetListOfKeys()) { + if (auto* sub = dynamic_cast(static_cast(key)->ReadObj())) { + obj = sub->Get(name); + if (obj) { + break; + } + } + } + } + auto* h = dynamic_cast(obj); + if (!h) { + std::printf("histogram %s not found\n", name); + return nullptr; + } + h->SetDirectory(nullptr); + return h; +} + +// fraction of BCs per bin: numerator / denominator (binomial errors) +TH1D* makeRatio(const TH1* num, const TH1* den, const char* name, const char* title) +{ + auto* r = static_cast(num->Clone(name)); + r->SetTitle(title); + r->Divide(num, den, 1., 1., "B"); + return r; +} + +// upper edge of the last filled bin (with 10% margin), to zoom the occupancy axes (e.g. for pp) +double upperEdgeOfFilledRange(const TH1* h) +{ + int last = 1; + for (int i = h->GetNbinsX(); i >= 1; i--) { + if (h->GetBinContent(i) > 0) { + last = i; + break; + } + } + return std::min(h->GetXaxis()->GetXmax(), 1.1 * h->GetXaxis()->GetBinUpEdge(last)); +} +} // namespace bcoccqa + +void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char* outPdf = "bcOccupancyQa.pdf", const char* dirName = "bc-occupancy-qa-task") +{ + using namespace bcoccqa; + TH1::AddDirectory(kFALSE); + gStyle->SetOptStat(0); + gStyle->SetPalette(kViridis); + gStyle->SetPaperSize(30., 20.); // landscape pages, same aspect ratio as the canvas + + std::unique_ptr f(TFile::Open(fileName, "READ")); + if (!f || f->IsZombie()) { + std::printf("cannot open %s\n", fileName); + return; + } + gDir = f->GetDirectory(dirName); + if (!gDir) { + std::printf("directory %s not found in %s\n", dirName, fileName); + return; + } + + auto* hBcCounter = getHist("hBcCounter"); + auto* hNumCollsInBC = getHist("hNumCollsInBC"); + auto* hBcInTF = getHist("hBcInTF"); + auto* hBcInTFDefined = getHist("hBcInTFOccupancyDefined"); + auto* hOccTracks = getHist("hOccupancyByTracks"); + auto* hOccFT0C = getHist("hOccupancyByFT0C"); + auto* hOccTracksVsFT0C = getHist("hOccupancyByTracksVsFT0C"); + auto* hMedianTime = getHist("hMedianTime"); + auto* pOccVsBcInTF = getHist("pOccupancyByTracksVsBcInTF"); + auto* pOccVsBcInOrbit = getHist("pOccupancyByTracksVsBcInOrbit"); + auto* hFlags = getHist("hOccupancyFlags"); + auto* hFlagsVsOcc = getHist("hOccupancyFlagsVsOccupancyByTracks"); + auto* hBcIndexCheck = getHist("hBcIndexCheck"); + + // ### summary + std::printf("\n=== BC occupancy QA: %s ===\n", fileName); + if (hBcCounter) { + double nAll = hBcCounter->GetBinContent(1), nDef = hBcCounter->GetBinContent(2); + std::printf("BCs: %.0f, with calculated occupancy: %.0f (%.2f%%)\n", nAll, nDef, nAll > 0 ? 100. * nDef / nAll : 0.); + } + if (hOccTracks && hOccFT0C) { + std::printf("mean occupancy: by ITS tracks %.1f, by FT0C %.1f\n", hOccTracks->GetMean(), hOccFT0C->GetMean()); + } + if (hNumCollsInBC) { + double nAll = hNumCollsInBC->GetEntries(); + std::printf("BCs with >= 1 collision: %.4f%%, with >= 2 collisions: %.4f%%\n", + nAll > 0 ? 100. * hNumCollsInBC->Integral(hNumCollsInBC->FindBin(1), hNumCollsInBC->GetNbinsX() + 1) / nAll : 0., + nAll > 0 ? 100. * hNumCollsInBC->Integral(hNumCollsInBC->FindBin(2), hNumCollsInBC->GetNbinsX() + 1) / nAll : 0.); + } + if (hFlags && hFlags->GetBinContent(1) > 0) { + std::printf("fraction of BCs (with calculated occupancy) passing each flag:\n"); + for (int i = 2; i <= hFlags->GetNbinsX(); i++) { + std::printf(" %-28s %.4f\n", hFlags->GetXaxis()->GetBinLabel(i), hFlags->GetBinContent(i) / hFlags->GetBinContent(1)); + } + } + + if (hBcIndexCheck) { + // all rows and all found BCs should be found at the right place + const bool ok = hBcIndexCheck->GetBinContent(2) == hBcIndexCheck->GetBinContent(1) && hBcIndexCheck->GetBinContent(5) == hBcIndexCheck->GetBinContent(4); + std::printf("lookup by global BC (BcOccAllIndex): rows found %.0f / %.0f, BCs of aod::BCs found %.0f / %.0f (not found %.0f), with the same global BC %.0f -> %s\n", + hBcIndexCheck->GetBinContent(2), hBcIndexCheck->GetBinContent(1), hBcIndexCheck->GetBinContent(4), hBcIndexCheck->GetBinContent(3), + hBcIndexCheck->GetBinContent(6), hBcIndexCheck->GetBinContent(5), ok ? "OK" : "MISMATCH"); + } + + // ### plots + const double maxOccTracks = hOccTracks ? upperEdgeOfFilledRange(hOccTracks) : 0.; + const double maxOccFT0C = hOccFT0C ? upperEdgeOfFilledRange(hOccFT0C) : 0.; + TCanvas c("c", "BC occupancy QA", 1200, 800); + c.Print(Form("%s[", outPdf)); + auto newPage = [&c](int nx, int ny) { + c.Clear(); + c.Divide(nx, ny); + }; + + // page 1: BC counters and the cross-check of the lookup by global BC + newPage(3, 1); + if (hBcCounter) { + c.cd(1)->SetLeftMargin(0.15); + hBcCounter->SetTitle("number of BCs"); + hBcCounter->Draw("hist text"); + } + if (hNumCollsInBC) { + c.cd(2)->SetLogy(); + hNumCollsInBC->SetTitle("all BCs;number of collisions found in the BC;N BCs"); + hNumCollsInBC->Draw("hist"); + } + if (hBcIndexCheck) { + c.cd(3)->SetBottomMargin(0.3); + c.cd(3)->SetLogy(); // rows of BcOccsAll vs (much fewer) BCs of aod::BCs + hBcIndexCheck->SetTitle("lookup in BcOccsAll by global BC;;counts"); + hBcIndexCheck->GetXaxis()->LabelsOption("v"); + hBcIndexCheck->SetMinimum(0.5); + hBcIndexCheck->Draw("hist text"); + } + c.Print(outPdf); + + // page 2: occupancy distributions + newPage(2, 2); + if (hOccTracks) { + c.cd(1)->SetLogy(); + hOccTracks->SetTitle("occupancy by ITS tracks;occupancy by ITS tracks;N BCs"); + hOccTracks->GetXaxis()->SetRangeUser(0., maxOccTracks); + hOccTracks->Draw("hist"); + } + if (hOccFT0C) { + c.cd(2)->SetLogy(); + hOccFT0C->SetTitle("occupancy by FT0C;occupancy by FT0C amplitudes;N BCs"); + hOccFT0C->GetXaxis()->SetRangeUser(0., maxOccFT0C); + hOccFT0C->Draw("hist"); + } + if (hOccTracksVsFT0C) { + c.cd(3)->SetLogz(); + hOccTracksVsFT0C->SetTitle("occupancy: ITS tracks vs FT0C;occupancy by FT0C amplitudes;occupancy by ITS tracks"); + hOccTracksVsFT0C->GetXaxis()->SetRangeUser(0., maxOccFT0C); + hOccTracksVsFT0C->GetYaxis()->SetRangeUser(0., maxOccTracks); + hOccTracksVsFT0C->Draw("colz"); + } + if (hMedianTime) { + c.cd(4)->SetLogy(); + hMedianTime->SetTitle("median time of the occupancy;#Deltat (#mus);N BCs"); + hMedianTime->Draw("hist"); + } + c.Print(outPdf); + + // page 3: occupancy flags + newPage(2, 1); + if (hFlags && hFlags->GetBinContent(1) > 0) { + c.cd(1)->SetBottomMargin(0.3); + auto* hFlagsFrac = static_cast(hFlags->Clone("hFlagsFrac")); + hFlagsFrac->Scale(1. / hFlags->GetBinContent(1)); + hFlagsFrac->SetTitle("fraction of BCs passing each flag;;fraction of BCs"); + hFlagsFrac->GetXaxis()->LabelsOption("v"); + hFlagsFrac->SetMinimum(0.); + hFlagsFrac->SetMaximum(1.1); + hFlagsFrac->Draw("hist text"); + } + if (hFlagsVsOcc) { + // fraction of BCs passing each flag vs occupancy + c.cd(2); + TLegend* leg = new TLegend(0.45, 0.6, 0.89, 0.89); + TH1D* hAll = hFlagsVsOcc->ProjectionY("hFlagsAll", 1, 1); + const int colors[] = {kRed + 1, kOrange + 7, kGreen + 2, kAzure + 2, kBlue + 2, kMagenta + 1, kBlack}; + bool first = true; + for (int i = 2; i <= hFlagsVsOcc->GetNbinsX(); i++) { + TH1D* hPass = hFlagsVsOcc->ProjectionY(Form("hFlagPass%d", i), i, i); + TH1D* hFrac = makeRatio(hPass, hAll, Form("hFlagFrac%d", i), "fraction of BCs passing the flags vs occupancy;occupancy by ITS tracks;fraction of BCs"); + hFrac->SetLineColor(colors[(i - 2) % 7]); + hFrac->SetMarkerColor(colors[(i - 2) % 7]); + hFrac->SetMarkerStyle(20); + hFrac->SetMarkerSize(0.4); + hFrac->SetMinimum(0.); + hFrac->SetMaximum(1.1); + hFrac->GetXaxis()->SetRangeUser(0., maxOccTracks); + hFrac->Draw(first ? "p" : "p same"); + leg->AddEntry(hFrac, hFlagsVsOcc->GetXaxis()->GetBinLabel(i), "p"); + first = false; + } + leg->Draw(); + } + c.Print(outPdf); + + // page 4: BC position in the TF and in the orbit + newPage(1, 3); + if (hBcInTF && hBcInTFDefined) { + c.cd(1); + TH1D* hFracDefined = makeRatio(hBcInTFDefined, hBcInTF, "hFracDefined", "fraction of BCs with calculated occupancy;BC in TF;fraction"); + hFracDefined->SetMinimum(0.); + hFracDefined->SetMaximum(1.1); + hFracDefined->Draw("hist"); + } + if (pOccVsBcInTF) { + c.cd(2); + pOccVsBcInTF->SetTitle("mean occupancy vs BC in TF;BC in TF;mean occupancy by ITS tracks"); + pOccVsBcInTF->Draw(); + } + if (pOccVsBcInOrbit) { + c.cd(3); + pOccVsBcInOrbit->SetTitle("mean occupancy vs BC in orbit;BC in orbit;mean occupancy by ITS tracks"); + pOccVsBcInOrbit->Draw("hist"); // 3564 bins: error bars would hide the pattern + } + c.Print(outPdf); + + c.Print(Form("%s]", outPdf)); + std::printf("plots saved in %s\n", outPdf); +} From 4abc96e5a8ca783dc6d3a5035c7e91056c6b231b Mon Sep 17 00:00:00 2001 From: Igor Altsybeev Date: Wed, 11 Nov 2026 12:24:01 +0100 Subject: [PATCH 07/14] add output to .root --- DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C | 107 +++++++++++++----- 1 file changed, 76 insertions(+), 31 deletions(-) diff --git a/DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C b/DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C index 48f70c28a01..9f9049d6d6e 100644 --- a/DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C +++ b/DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C @@ -14,7 +14,8 @@ /// /// Usage: /// root -l -b -q 'plotBcOccupancyQa.C("AnalysisResults.root")' -/// root -l -b -q 'plotBcOccupancyQa.C("AnalysisResults.root", "bcOccupancyQa.pdf", "bc-occupancy-qa-task")' +/// root -l -b -q 'plotBcOccupancyQa.C("AnalysisResults.root", "bcOccupancyQa.pdf", "bc-occupancy-qa-task", "bcOccupancyQa.root")' +/// Output: multi-page PDF, and a ROOT file with the canvases and the histograms (by default: name of the PDF with .root) /// /// \author Igor Altsybeev @@ -60,6 +61,7 @@ T* getHist(const char* name) return nullptr; } h->SetDirectory(nullptr); + h->SetStats(false); // also when the saved canvases are drawn later return h; } @@ -69,6 +71,7 @@ TH1D* makeRatio(const TH1* num, const TH1* den, const char* name, const char* ti auto* r = static_cast(num->Clone(name)); r->SetTitle(title); r->Divide(num, den, 1., 1., "B"); + r->SetStats(false); return r; } @@ -86,13 +89,12 @@ double upperEdgeOfFilledRange(const TH1* h) } } // namespace bcoccqa -void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char* outPdf = "bcOccupancyQa.pdf", const char* dirName = "bc-occupancy-qa-task") +void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char* outPdf = "bcOccupancyQa.pdf", const char* dirName = "bc-occupancy-qa-task", const char* outRoot = "") { using namespace bcoccqa; TH1::AddDirectory(kFALSE); gStyle->SetOptStat(0); gStyle->SetPalette(kViridis); - gStyle->SetPaperSize(30., 20.); // landscape pages, same aspect ratio as the canvas std::unique_ptr f(TFile::Open(fileName, "READ")); if (!f || f->IsZombie()) { @@ -152,67 +154,69 @@ void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char // ### plots const double maxOccTracks = hOccTracks ? upperEdgeOfFilledRange(hOccTracks) : 0.; const double maxOccFT0C = hOccFT0C ? upperEdgeOfFilledRange(hOccFT0C) : 0.; - TCanvas c("c", "BC occupancy QA", 1200, 800); - c.Print(Form("%s[", outPdf)); - auto newPage = [&c](int nx, int ny) { - c.Clear(); - c.Divide(nx, ny); + // one canvas per page, with the size adapted to the number of pads + std::vector canvases; + std::vector derivedHists; // histograms made here (fractions), also saved to the ROOT file + auto newPage = [&canvases](const char* name, const char* title, int width, int height, int nx, int ny) { + auto* canvas = new TCanvas(name, title, width, height); + canvas->Divide(nx, ny); + canvases.push_back(canvas); + return canvas; }; + TCanvas* c = nullptr; // page 1: BC counters and the cross-check of the lookup by global BC - newPage(3, 1); + c = newPage("cBcCounters", "BC counters", 1500, 550, 3, 1); if (hBcCounter) { - c.cd(1)->SetLeftMargin(0.15); + c->cd(1)->SetLeftMargin(0.15); hBcCounter->SetTitle("number of BCs"); hBcCounter->Draw("hist text"); } if (hNumCollsInBC) { - c.cd(2)->SetLogy(); + c->cd(2)->SetLogy(); hNumCollsInBC->SetTitle("all BCs;number of collisions found in the BC;N BCs"); hNumCollsInBC->Draw("hist"); } if (hBcIndexCheck) { - c.cd(3)->SetBottomMargin(0.3); - c.cd(3)->SetLogy(); // rows of BcOccsAll vs (much fewer) BCs of aod::BCs + c->cd(3)->SetBottomMargin(0.3); + c->cd(3)->SetLogy(); // rows of BcOccsAll vs (much fewer) BCs of aod::BCs hBcIndexCheck->SetTitle("lookup in BcOccsAll by global BC;;counts"); hBcIndexCheck->GetXaxis()->LabelsOption("v"); hBcIndexCheck->SetMinimum(0.5); hBcIndexCheck->Draw("hist text"); } - c.Print(outPdf); // page 2: occupancy distributions - newPage(2, 2); + c = newPage("cOccupancy", "occupancy distributions", 1200, 800, 2, 2); if (hOccTracks) { - c.cd(1)->SetLogy(); + c->cd(1)->SetLogy(); hOccTracks->SetTitle("occupancy by ITS tracks;occupancy by ITS tracks;N BCs"); hOccTracks->GetXaxis()->SetRangeUser(0., maxOccTracks); hOccTracks->Draw("hist"); } if (hOccFT0C) { - c.cd(2)->SetLogy(); + c->cd(2)->SetLogy(); hOccFT0C->SetTitle("occupancy by FT0C;occupancy by FT0C amplitudes;N BCs"); hOccFT0C->GetXaxis()->SetRangeUser(0., maxOccFT0C); hOccFT0C->Draw("hist"); } if (hOccTracksVsFT0C) { - c.cd(3)->SetLogz(); + c->cd(3)->SetLogz(); hOccTracksVsFT0C->SetTitle("occupancy: ITS tracks vs FT0C;occupancy by FT0C amplitudes;occupancy by ITS tracks"); hOccTracksVsFT0C->GetXaxis()->SetRangeUser(0., maxOccFT0C); hOccTracksVsFT0C->GetYaxis()->SetRangeUser(0., maxOccTracks); hOccTracksVsFT0C->Draw("colz"); } if (hMedianTime) { - c.cd(4)->SetLogy(); + c->cd(4)->SetLogy(); hMedianTime->SetTitle("median time of the occupancy;#Deltat (#mus);N BCs"); hMedianTime->Draw("hist"); } - c.Print(outPdf); // page 3: occupancy flags - newPage(2, 1); + c = newPage("cOccupancyFlags", "occupancy flags", 1200, 550, 2, 1); if (hFlags && hFlags->GetBinContent(1) > 0) { - c.cd(1)->SetBottomMargin(0.3); + c->cd(1)->SetBottomMargin(0.3); auto* hFlagsFrac = static_cast(hFlags->Clone("hFlagsFrac")); hFlagsFrac->Scale(1. / hFlags->GetBinContent(1)); hFlagsFrac->SetTitle("fraction of BCs passing each flag;;fraction of BCs"); @@ -220,17 +224,18 @@ void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char hFlagsFrac->SetMinimum(0.); hFlagsFrac->SetMaximum(1.1); hFlagsFrac->Draw("hist text"); + derivedHists.push_back(hFlagsFrac); } if (hFlagsVsOcc) { // fraction of BCs passing each flag vs occupancy - c.cd(2); + c->cd(2); TLegend* leg = new TLegend(0.45, 0.6, 0.89, 0.89); TH1D* hAll = hFlagsVsOcc->ProjectionY("hFlagsAll", 1, 1); const int colors[] = {kRed + 1, kOrange + 7, kGreen + 2, kAzure + 2, kBlue + 2, kMagenta + 1, kBlack}; bool first = true; for (int i = 2; i <= hFlagsVsOcc->GetNbinsX(); i++) { TH1D* hPass = hFlagsVsOcc->ProjectionY(Form("hFlagPass%d", i), i, i); - TH1D* hFrac = makeRatio(hPass, hAll, Form("hFlagFrac%d", i), "fraction of BCs passing the flags vs occupancy;occupancy by ITS tracks;fraction of BCs"); + TH1D* hFrac = makeRatio(hPass, hAll, Form("hFracVsOccupancy_%s", hFlagsVsOcc->GetXaxis()->GetBinLabel(i)), "fraction of BCs passing the flags vs occupancy;occupancy by ITS tracks;fraction of BCs"); hFrac->SetLineColor(colors[(i - 2) % 7]); hFrac->SetMarkerColor(colors[(i - 2) % 7]); hFrac->SetMarkerStyle(20); @@ -239,34 +244,74 @@ void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char hFrac->SetMaximum(1.1); hFrac->GetXaxis()->SetRangeUser(0., maxOccTracks); hFrac->Draw(first ? "p" : "p same"); + derivedHists.push_back(hFrac); leg->AddEntry(hFrac, hFlagsVsOcc->GetXaxis()->GetBinLabel(i), "p"); first = false; } leg->Draw(); } - c.Print(outPdf); // page 4: BC position in the TF and in the orbit - newPage(1, 3); + c = newPage("cBcPosition", "BC position in the TF and in the orbit", 1200, 800, 1, 3); if (hBcInTF && hBcInTFDefined) { - c.cd(1); + c->cd(1); TH1D* hFracDefined = makeRatio(hBcInTFDefined, hBcInTF, "hFracDefined", "fraction of BCs with calculated occupancy;BC in TF;fraction"); hFracDefined->SetMinimum(0.); hFracDefined->SetMaximum(1.1); hFracDefined->Draw("hist"); + derivedHists.push_back(hFracDefined); } if (pOccVsBcInTF) { - c.cd(2); + c->cd(2); pOccVsBcInTF->SetTitle("mean occupancy vs BC in TF;BC in TF;mean occupancy by ITS tracks"); pOccVsBcInTF->Draw(); } if (pOccVsBcInOrbit) { - c.cd(3); + c->cd(3); pOccVsBcInOrbit->SetTitle("mean occupancy vs BC in orbit;BC in orbit;mean occupancy by ITS tracks"); pOccVsBcInOrbit->Draw("hist"); // 3564 bins: error bars would hide the pattern } - c.Print(outPdf); - c.Print(Form("%s]", outPdf)); + // PDF: each page with the aspect ratio of its canvas + for (size_t i = 0; i < canvases.size(); i++) { + gStyle->SetPaperSize(30., 30. * canvases[i]->GetWh() / canvases[i]->GetWw()); + if (i == 0) { + canvases[i]->Print(Form("%s[", outPdf)); + } + canvases[i]->Print(outPdf); + if (i == canvases.size() - 1) { + canvases[i]->Print(Form("%s]", outPdf)); + } + } std::printf("plots saved in %s\n", outPdf); + + // ROOT file: canvases and histograms (from the QA task and the fractions made here) + TString rootName = outRoot; + if (rootName.IsNull()) { + rootName = outPdf; + if (rootName.EndsWith(".pdf")) { + rootName.Resize(rootName.Length() - 4); + } + rootName += ".root"; + } + std::unique_ptr fOut(TFile::Open(rootName, "RECREATE")); + if (!fOut || fOut->IsZombie()) { + std::printf("cannot create %s\n", rootName.Data()); + return; + } + fOut->mkdir("canvases")->cd(); + for (auto* canvas : canvases) { + canvas->Write(); + } + fOut->mkdir("histograms")->cd(); + for (TH1* h : {static_cast(hBcCounter), hNumCollsInBC, hBcInTF, hBcInTFDefined, hOccTracks, hOccFT0C, static_cast(hOccTracksVsFT0C), hMedianTime, + static_cast(pOccVsBcInTF), static_cast(pOccVsBcInOrbit), hFlags, static_cast(hFlagsVsOcc), hBcIndexCheck}) { + if (h) { + h->Write(); + } + } + for (TH1* h : derivedHists) { + h->Write(); + } + std::printf("canvases and histograms saved in %s\n", rootName.Data()); } From 42fba83b74f1e189346d38feee12940ef3d59224 Mon Sep 17 00:00:00 2001 From: Igor Altsybeev Date: Wed, 11 Nov 2026 15:51:24 +0100 Subject: [PATCH 08/14] add NumCollsInRof column and upcMode flag for BC tables --- Common/DataModel/EventSelection.h | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Common/DataModel/EventSelection.h b/Common/DataModel/EventSelection.h index e1636de2c59..955b9e1faa8 100644 --- a/Common/DataModel/EventSelection.h +++ b/Common/DataModel/EventSelection.h @@ -103,13 +103,15 @@ enum BcOccupancyFlags { // bits of occupancyFlags kNoCollInTimeRangeStandard, // no other collisions in specified time range with per-collision multiplicity above threshold kNoCollInRofStrict, // no other collisions in this ITS Readout Frame kNoCollInRofStandard, // no other collisions in this ITS Readout Frame with per-collision multiplicity above threshold (no veto on collisions with close vZ) - kNoHighMultCollInPrevRof // FT0C amplitude in previous ITS ROF is below threshold + kNoHighMultCollInPrevRof, // FT0C amplitude in previous ITS ROF is below threshold + kUpcMode // ITS ROF of this BC is in UPC mode: no collisions in the ROF, or collisions reconstructed in the UPC mode (set for all BCs, also without calculated occupancy) }; DECLARE_SOA_COLUMN(NumTracksInTimeRange, trackOccupancyInTimeRange, int); //! Occupancy in specified time interval by a number of tracks from nearby collisions (-1 if not available) // o2-linter: disable=name/o2-column DECLARE_SOA_COLUMN(SumAmpFT0CInTimeRange, ft0cOccupancyInTimeRange, float); //! Occupancy in specified time interval by a sum of FT0C amplitudes from nearby collisions (-1 if not available) // o2-linter: disable=name/o2-column DECLARE_SOA_COLUMN(OccupancyMedianTime, occupancyMedianTime, float); //! Median time for the occupancy in specified time interval (the delta-time wrt to the BC at which occupancy crosses 1/2 of its full value) DECLARE_SOA_BITMAP_COLUMN(OccupancyFlags, occupancyFlags, 8); //! Bitmask of occupancy flags (see BcOccupancyFlags), check with occupancyFlags_bit(bit) DECLARE_SOA_COLUMN(NumCollsInBC, numCollsInBC, int16_t); //! Number of collisions found in this BC (excluded from the occupancy) +DECLARE_SOA_COLUMN(NumCollsInRof, numCollsInRof, int16_t); //! Number of collisions found in the ITS ROF of this BC (within the TF), including the collisions in this BC (-1 if the ROF parameters are not available) } // namespace bcocc // bc-joinable occupancy @@ -118,7 +120,8 @@ DECLARE_SOA_TABLE(BcOccs, "AOD", "BCOCC", //! bcocc::SumAmpFT0CInTimeRange, bcocc::OccupancyMedianTime, bcocc::OccupancyFlags, - bcocc::NumCollsInBC); + bcocc::NumCollsInBC, + bcocc::NumCollsInRof); using BcOcc = BcOccs::iterator; // occupancy wrt every BC of the TFs present in the DF (not joinable): for each TF (in increasing order) all its BCs, in increasing order of global BC @@ -129,7 +132,8 @@ DECLARE_SOA_TABLE(BcOccsAll, "AOD", "BCOCCALL", //! bcocc::SumAmpFT0CInTimeRange, bcocc::OccupancyMedianTime, bcocc::OccupancyFlags, - bcocc::NumCollsInBC); + bcocc::NumCollsInBC, + bcocc::NumCollsInRof); using BcOccAll = BcOccsAll::iterator; namespace bcocc @@ -146,10 +150,12 @@ class BcOccAllIndex explicit BcOccAllIndex(TBcOccsAll const& bcOccsAll) : mTable(&bcOccsAll) { const int64_t nRows = static_cast(bcOccsAll.size()); + // offset globalBC - row: constant along consecutive BCs, changes only where TFs are missing in the DF auto offsetAt = [&bcOccsAll](int64_t row) { return static_cast(bcOccsAll.iteratorAt(row).globalBC()) - row; }; for (int64_t first = 0, end = 0; first < nRows; first = end) { // the offset is non-decreasing (global BCs are strictly increasing): the segment ends at the first row with a larger offset const int64_t offset = offsetAt(first); + // binary search over the rows [first, nRows) for the end of the segment: the first row with a different offset end = *std::ranges::partition_point(std::views::iota(first, nRows), [&](int64_t row) { return offsetAt(row) == offset; }); mSegments.push_back({offset + first, offset + end, offset}); } From 555b495353bdd2e5bfd4082ce41b60ba3c3867a1 Mon Sep 17 00:00:00 2001 From: Igor Altsybeev Date: Wed, 11 Nov 2026 15:52:08 +0100 Subject: [PATCH 09/14] add NumCollsInRof column and upcMode flag for BC tables --- Common/Tools/EventSelectionModule.h | 206 ++++++++++++++++++---------- 1 file changed, 132 insertions(+), 74 deletions(-) diff --git a/Common/Tools/EventSelectionModule.h b/Common/Tools/EventSelectionModule.h index eba03d8a34f..562b7efe543 100644 --- a/Common/Tools/EventSelectionModule.h +++ b/Common/Tools/EventSelectionModule.h @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -37,6 +38,7 @@ #include #include #include +#include #include #include @@ -635,20 +637,21 @@ class BcSelectionModule }; // end BcSelectionModule // Occupancy calculation (used by EventSelectionModule) -// The occupancy wrt a reference point is calculated from other collisions ("sources") nearby in time: +// The occupancy wrt a reference point is calculated from other collisions nearby in time: // in the same ITS ROF, in the previous ITS ROF and in a time window around the reference. class OccupancyCalculator { public: // per-collision input for the occupancy calculation - struct Source { + struct Collision { int64_t globalBC = 0; // global BC of the collision (after the collision-BC matching) int nTracksITS567 = 0; // number of PV contributors with >= 5 ITS clusters int proxyNtracks = 0; // nTracksITS567, or FT0C/10 if the collision is close to an ITS ROF border float ampFT0C = 0; // FT0C amplitude in the found BC float vZ = 0; // z of the primary vertex bool isRejectedByTFborderCut = false; // the found BC is close to a TF border (kNoTimeFrameBorder is not set) + bool isUpcMode = false; // the vertex was reconstructed in the UPC mode of ITS (Vertex::Flags::UPCMode) }; // occupancy estimators and flags wrt a given reference @@ -665,16 +668,17 @@ class OccupancyCalculator bool noHighMultCollInPrevRof = false; // total FT0C amplitude in the previous ITS ROF below FT0CamplPerCollCutVetoOnCollInROF }; - // prepare n sources (one per collision, same order as in the collision table) with all fields reset, + // prepare n collisions (same order as in the collision table) with all fields reset, // and clear the list of TVX BCs (all per-DF inputs) - void resetSources(size_t n) + void resetCollisions(size_t n) { - mSources.assign(n, Source{}); + mCollisions.assign(n, Collision{}); mTVXBCsForTFBorderEstimate.clear(); mIsReadyForBCQueries = false; + mLastRofId = -1; } - // source i (for filling) - Source& source(int32_t i) { return mSources[i]; } + // collision i (for filling) + Collision& collision(int32_t i) { return mCollisions[i]; } // set run-dependent constants and configurables template @@ -709,49 +713,69 @@ class OccupancyCalculator } // colliding BCs with TVX and their FT0C amplitude, for the FT0C-based occupancy estimate near TF borders - // to be filled once per DF (after resetSources), in increasing order of global BC + // to be filled once per DF (after resetCollisions), in increasing order of global BC void addTVXBC(int64_t globalBC, float ampFT0C) { mTVXBCsForTFBorderEstimate.push_back({globalBC, ampFT0C}); } // ampFT0C = -1 if there is no FT0 in this BC - // occupancy wrt mSources[iRef] (the source itself is excluded) - Result calculateForSource(int32_t iRef) + // occupancy wrt mCollisions[iRef] (the collision itself is excluded) + Result calculateForCollision(int32_t iRef) { - return calculate(mSources, mSources[iRef].globalBC, mSources[iRef].vZ, iRef - 1, iRef + 1); + return calculate(mCollisions, mCollisions[iRef].globalBC, mCollisions[iRef].vZ, iRef - 1, iRef + 1); } - // prepare queries wrt BCs (numSourcesInBC, calculateForBC): copy of the sources sorted by global BC - // to be called after the sources are filled (done only once per DF) + // prepare queries wrt BCs (numCollisionsInBC, calculateForBC): copy of the collisions sorted by global BC + // to be called after the collisions are filled (done only once per DF) void prepareBCQueries() { if (mIsReadyForBCQueries) { return; } - mSourcesSortedByBC = mSources; - std::stable_sort(mSourcesSortedByBC.begin(), mSourcesSortedByBC.end(), [](const Source& a, const Source& b) { return a.globalBC < b.globalBC; }); + mCollisionsSortedByBC = mCollisions; + std::stable_sort(mCollisionsSortedByBC.begin(), mCollisionsSortedByBC.end(), [](const Collision& a, const Collision& b) { return a.globalBC < b.globalBC; }); mIsReadyForBCQueries = true; } - // number of sources (collisions) with a given global BC (requires prepareBCQueries) - int numSourcesInBC(int64_t globalBC) const + // number of collisions with a given global BC (requires prepareBCQueries) + int numCollisionsInBC(int64_t globalBC) const { - auto [iFirst, iEnd] = getSortedSourceRangeInBC(globalBC); + auto [iFirst, iEnd] = getSortedCollisionRangeInBC(globalBC); return iEnd - iFirst; } + // number of collisions in the ITS ROF of a given global BC, within its TF, including the collisions in this BC (requires prepareBCQueries) + // -1 if the ROF or TF parameters are not available (e.g. unanchored MC) + int numCollisionsInRof(int64_t globalBC) + { + if (!updateRofInfo(globalBC)) { + return -1; + } + return mLastRofNumCollisions; + } + + // the ITS ROF of a given global BC is in UPC mode: no collisions in the ROF, or collisions reconstructed in the UPC mode (requires prepareBCQueries) + // false if the ROF or TF parameters are not available (e.g. unanchored MC) + bool isRofInUpcMode(int64_t globalBC) + { + if (!updateRofInfo(globalBC)) { + return false; + } + return mLastRofNumCollisions == 0 || mLastRofNumUpcCollisions > 0; + } + // occupancy wrt a BC (requires prepareBCQueries): - // sources (collisions) in this BC are excluded, the in-ROF veto on collisions with close vZ is not applied (no vZ) + // collisions in this BC are excluded, the in-ROF veto on collisions with close vZ is not applied (no vZ) Result calculateForBC(int64_t globalBC) { - auto [iFirst, iEnd] = getSortedSourceRangeInBC(globalBC); - return calculate(mSourcesSortedByBC, globalBC, std::numeric_limits::quiet_NaN(), iFirst - 1, iEnd); + auto [iFirst, iEnd] = getSortedCollisionRangeInBC(globalBC); + return calculate(mCollisionsSortedByBC, globalBC, std::numeric_limits::quiet_NaN(), iFirst - 1, iEnd); } private: // occupancy wrt refGlobalBC (refVz is used for the in-ROF veto on collisions with close vZ, NaN = veto not applied) - // srcs are scanned backward starting from index iBefore and forward starting from index iAfter - Result calculate(const std::vector& srcs, int64_t refGlobalBC, float refVz, int32_t iBefore, int32_t iAfter) + // colls are scanned backward starting from index iBefore and forward starting from index iAfter + Result calculate(const std::vector& colls, int64_t refGlobalBC, float refVz, int32_t iBefore, int32_t iAfter) { Result res; - const int32_t nSources = srcs.size(); + const int32_t nColls = colls.size(); const int64_t tfId = getTFid(refGlobalBC); const int64_t rofId = getRofId(refGlobalBC); const int bcInTF = (refGlobalBC - mBcSOR) % mNBCsPerTF; @@ -764,25 +788,25 @@ class OccupancyCalculator int nITS567tracksForSameRofVetoStrict = 0; // to veto events with other collisions in the same ITS ROF int nCollsInRofWithFT0CAboveVetoStandard = 0; // to veto events with other collisions in the same ITS ROF, with per-collision multiplicity above threshold int nITS567tracksForRofVetoOnCloseVz = 0; // to veto events with nearby collisions with close vZ - auto addCollInSameRof = [&](const Source& src) { - nITS567tracksForSameRofVetoStrict += src.nTracksITS567; - if (src.ampFT0C > mFT0CamplCutVetoOnCollInROF) + auto addCollInSameRof = [&](const Collision& coll) { + nITS567tracksForSameRofVetoStrict += coll.nTracksITS567; + if (coll.ampFT0C > mFT0CamplCutVetoOnCollInROF) nCollsInRofWithFT0CAboveVetoStandard++; - if (std::fabs(src.vZ - refVz) < mEpsilonVzDiffVetoInROF) - nITS567tracksForRofVetoOnCloseVz += src.nTracksITS567; + if (std::fabs(coll.vZ - refVz) < mEpsilonVzDiffVetoInROF) + nITS567tracksForRofVetoOnCloseVz += coll.nTracksITS567; }; // collisions in the same TF and ROF before and after the reference for (int32_t i = iBefore; i >= 0; i--) { - int64_t thisBC = srcs[i].globalBC; + int64_t thisBC = colls[i].globalBC; if (getTFid(thisBC) != tfId || getRofId(thisBC) != rofId) break; - addCollInSameRof(srcs[i]); + addCollInSameRof(colls[i]); } - for (int32_t i = iAfter; i < nSources; i++) { - int64_t thisBC = srcs[i].globalBC; + for (int32_t i = iAfter; i < nColls; i++) { + int64_t thisBC = colls[i].globalBC; if (getTFid(thisBC) != tfId || getRofId(thisBC) != rofId) break; - addCollInSameRof(srcs[i]); + addCollInSameRof(colls[i]); } res.noCollInRofStrict = (nITS567tracksForSameRofVetoStrict == 0); res.noCollInRofStandard = (nCollsInRofWithFT0CAboveVetoStandard == 0); @@ -791,12 +815,12 @@ class OccupancyCalculator // ### occupancy in the previous ROF float totalFT0amplInPrevROF = 0; for (int32_t i = iBefore; i >= 0; i--) { - int64_t thisBC = srcs[i].globalBC; + int64_t thisBC = colls[i].globalBC; if (getTFid(thisBC) != tfId) break; int64_t thisRofId = getRofId(thisBC); if (thisRofId == rofId - 1) - totalFT0amplInPrevROF += srcs[i].ampFT0C; + totalFT0amplInPrevROF += colls[i].ampFT0C; else if (thisRofId < rofId - 1) break; } @@ -813,24 +837,24 @@ class OccupancyCalculator int proxyTotalMultInTimeWin = 0; mDeltaTimeMult.clear(); // (delta time in ns, proxy for N tracks) of collisions in the time window, for the median time auto addCollInTimeWin = [&](int32_t i, float dtNS) { - const Source& src = srcs[i]; - mDeltaTimeMult.emplace_back(dtNS, src.proxyNtracks); - proxyTotalMultInTimeWin = proxyTotalMultInTimeWin + static_cast(src.proxyNtracks); // int + float, as in the original std::accumulate over floats + const Collision& coll = colls[i]; + mDeltaTimeMult.emplace_back(dtNS, coll.proxyNtracks); + proxyTotalMultInTimeWin = proxyTotalMultInTimeWin + static_cast(coll.proxyNtracks); // int + float, as in the original std::accumulate over floats float dt = dtNS / 1e3; // ns -> us // counting tracks from other collisions in fixed time windows if (std::fabs(dt) < mTimeRangeVetoOnCollNarrow) - nITS567tracksForVetoNarrow += src.proxyNtracks; + nITS567tracksForVetoNarrow += coll.proxyNtracks; if (std::fabs(dt) < mTimeRangeVetoOnCollStrict) - nITS567tracksForVetoStrict += src.proxyNtracks; + nITS567tracksForVetoStrict += coll.proxyNtracks; // veto on high-mult collisions nearby, where artificial structures in the dt-occupancy plots are observed - if (dt > -4.0 && dt < 2.0 && src.ampFT0C > mFT0CamplCutVetoOnCollInTimeRange) { // o2-linter: disable=magic-number (dt in us, time window for high-mult veto) + if (dt > -4.0 && dt < 2.0 && coll.ampFT0C > mFT0CamplCutVetoOnCollInTimeRange) { // o2-linter: disable=magic-number (dt in us, time window for high-mult veto) nCollsWithFT0CAboveVetoStandard++; } // check if we are close to TF borders => N ITS tracks is not reliable, and FT0C ampl will be used for occupancy estimation (see below) - if (src.isRejectedByTFborderCut) { + if (coll.isRejectedByTFborderCut) { if (iFirstRejectedByTFborderCut == -1) iFirstRejectedByTFborderCut = i; return; @@ -842,13 +866,13 @@ class OccupancyCalculator if (mUseWeightsForOccupancy) { wOccup = calcWeightForOccupancy(dt); } - nITS567tracksInFullTimeWindow += wOccup * src.proxyNtracks; - sumAmpFT0CInFullTimeWindow += wOccup * src.ampFT0C; + nITS567tracksInFullTimeWindow += wOccup * coll.proxyNtracks; + sumAmpFT0CInFullTimeWindow += wOccup * coll.ampFT0C; } }; // collisions in the time window before and after the reference for (int32_t i = iBefore; i >= 0; i--) { - int64_t thisBC = srcs[i].globalBC; + int64_t thisBC = colls[i].globalBC; if (getTFid(thisBC) != tfId) break; float dt = (thisBC - refGlobalBC) * bcNS; // ns @@ -856,8 +880,8 @@ class OccupancyCalculator break; addCollInTimeWin(i, dt); } - for (int32_t i = iAfter; i < nSources; i++) { - int64_t thisBC = srcs[i].globalBC; + for (int32_t i = iAfter; i < nColls; i++) { + int64_t thisBC = colls[i].globalBC; if (getTFid(thisBC) != tfId) break; float dt = (thisBC - refGlobalBC) * bcNS; // ns @@ -884,7 +908,7 @@ class OccupancyCalculator // if some collisions in the time window are close to the TF border - take FT0C amplitude instead of nTracks, using TVX BCs // starting from the BC of the first such collision (only if it is a TVX BC itself) if (isFullInfoForOccupancy && canHaveAssocCollsWithinLastDriftTime && iFirstRejectedByTFborderCut >= 0) { - const int64_t firstGlobalBC = srcs[iFirstRejectedByTFborderCut].globalBC; + const int64_t firstGlobalBC = colls[iFirstRejectedByTFborderCut].globalBC; auto it = std::lower_bound(mTVXBCsForTFBorderEstimate.begin(), mTVXBCsForTFBorderEstimate.end(), firstGlobalBC, [](const TVXBC& tvx, int64_t bc) { return tvx.globalBC < bc; }); if (it != mTVXBCsForTFBorderEstimate.end() && it->globalBC != firstGlobalBC) { it = mTVXBCsForTFBorderEstimate.end(); // exact match required @@ -924,17 +948,46 @@ class OccupancyCalculator return res; } - // index range [first, end) of the sources with a given global BC in mSourcesSortedByBC - std::pair getSortedSourceRangeInBC(int64_t globalBC) const + // collisions in the ITS ROF of a given global BC (same ROF convention as the in-ROF vetoes, cut at the TF borders): + // the result for the last ROF is kept, since consecutive queries are often in the same ROF; false if the ROF or TF parameters are not available + bool updateRofInfo(int64_t globalBC) + { + if (mRofLength <= 0 || mNBCsPerTF <= 0) { + return false; + } + const int64_t rofId = getRofId(globalBC); + const int64_t tfId = getTFid(globalBC); + if (rofId != mLastRofId || tfId != mLastRofTfId) { + // BC range [rofStart, rofEnd) of the ROF, cut at the TF borders + const int64_t tfStart = mBcSOR + tfId * mNBCsPerTF; + const int64_t rofStart = std::max(rofId * mRofLength + mRofOffset - nBCsPerOrbit, tfStart); + const int64_t rofEnd = std::min(rofId * mRofLength + mRofOffset - nBCsPerOrbit + mRofLength, tfStart + mNBCsPerTF); + auto isBefore = [](const Collision& coll, int64_t bc) { return coll.globalBC < bc; }; + auto itFirst = std::lower_bound(mCollisionsSortedByBC.begin(), mCollisionsSortedByBC.end(), rofStart, isBefore); + auto itEnd = rofEnd > rofStart ? std::lower_bound(itFirst, mCollisionsSortedByBC.end(), rofEnd, isBefore) : itFirst; + mLastRofId = rofId; + mLastRofTfId = tfId; + mLastRofNumCollisions = static_cast(itEnd - itFirst); + mLastRofNumUpcCollisions = static_cast(std::count_if(itFirst, itEnd, [](const Collision& coll) { return coll.isUpcMode; })); + } + return true; + } + + // index range [first, end) of the collisions with a given global BC in mCollisionsSortedByBC + std::pair getSortedCollisionRangeInBC(int64_t globalBC) const { - auto itFirst = std::lower_bound(mSourcesSortedByBC.begin(), mSourcesSortedByBC.end(), globalBC, [](const Source& src, int64_t bc) { return src.globalBC < bc; }); - auto itEnd = std::upper_bound(itFirst, mSourcesSortedByBC.end(), globalBC, [](int64_t bc, const Source& src) { return bc < src.globalBC; }); - return {static_cast(itFirst - mSourcesSortedByBC.begin()), static_cast(itEnd - mSourcesSortedByBC.begin())}; + auto itFirst = std::lower_bound(mCollisionsSortedByBC.begin(), mCollisionsSortedByBC.end(), globalBC, [](const Collision& coll, int64_t bc) { return coll.globalBC < bc; }); + auto itEnd = std::upper_bound(itFirst, mCollisionsSortedByBC.end(), globalBC, [](int64_t bc, const Collision& coll) { return bc < coll.globalBC; }); + return {static_cast(itFirst - mCollisionsSortedByBC.begin()), static_cast(itEnd - mCollisionsSortedByBC.begin())}; } - std::vector mSources; // one entry per collision, same order as in the collision table - std::vector mSourcesSortedByBC; // copy of mSources sorted by global BC, for queries wrt BCs - bool mIsReadyForBCQueries = false; // mSourcesSortedByBC is prepared for the current sources + std::vector mCollisions; // one entry per collision, same order as in the collision table + std::vector mCollisionsSortedByBC; // copy of mCollisions sorted by global BC, for queries wrt BCs + bool mIsReadyForBCQueries = false; // mCollisionsSortedByBC is prepared for the current collisions + int64_t mLastRofId = -1; // last ROF queried in updateRofInfo (-1: none) + int64_t mLastRofTfId = -1; // TF of the last ROF queried + int mLastRofNumUpcCollisions = 0; // number of collisions reconstructed in the UPC mode in the last ROF queried + int mLastRofNumCollisions = 0; // number of collisions in the last ROF queried struct TVXBC { int64_t globalBC = 0; // global BC of a colliding BC with TVX @@ -1519,23 +1572,24 @@ class EventSelectionModule } // fill per-collision inputs for the occupancy calculation - occupancyCalc.resetSources(cols.size()); // per-collision inputs for the occupancy calculation + occupancyCalc.resetCollisions(cols.size()); // per-collision inputs for the occupancy calculation for (const auto& col : cols) { int32_t colIndex = col.globalIndex(); - OccupancyCalculator::Source& occupSrc = occupancyCalc.source(colIndex); - occupSrc.vZ = col.posZ(); - occupSrc.nTracksITS567 = vNumTracksITS567[colIndex]; - occupSrc.globalBC = vFoundGlobalBC[colIndex]; + OccupancyCalculator::Collision& occupColl = occupancyCalc.collision(colIndex); + occupColl.vZ = col.posZ(); + occupColl.nTracksITS567 = vNumTracksITS567[colIndex]; + occupColl.globalBC = vFoundGlobalBC[colIndex]; const auto& bcselEntry = bcselbuffer[vFoundBCindex[colIndex]]; if (bcselEntry.foundFT0Id > -1) { // required: explicit ft0s table auto foundFT0 = ft0s.rawIteratorAt(bcselEntry.foundFT0Id); - occupSrc.ampFT0C = foundFT0.sumAmpC(); + occupColl.ampFT0C = foundFT0.sumAmpC(); } // check if we are close to ITS ROF borders => N ITS tracks is not reliable, and FT0C ampl can be used for occupancy estimation // denominator for ampFT0C is the approximate conversion factor b/n FT0C ampl and number of PV tracks after cuts - occupSrc.proxyNtracks = BITCHECK64(bcselEntry.selection, aod::evsel::kNoITSROFrameBorder) ? occupSrc.nTracksITS567 : occupSrc.ampFT0C / 10.; - occupSrc.isRejectedByTFborderCut = !BITCHECK64(bcselEntry.selection, aod::evsel::kNoTimeFrameBorder); + occupColl.proxyNtracks = BITCHECK64(bcselEntry.selection, aod::evsel::kNoITSROFrameBorder) ? occupColl.nTracksITS567 : occupColl.ampFT0C / 10.; + occupColl.isRejectedByTFborderCut = !BITCHECK64(bcselEntry.selection, aod::evsel::kNoTimeFrameBorder); + occupColl.isUpcMode = (col.flags() & o2::dataformats::Vertex>::Flags::UPCMode) != 0; } // colliding TVX BCs with their FT0C amplitude, for the occupancy estimation near TF borders for (const auto& [globalBC, bcIndex] : mapGlobalBcWithTVX) { @@ -1556,7 +1610,7 @@ class EventSelectionModule int32_t foundZDC = bcselEntry.foundZDCId; // occupancy estimators and flags wrt this collision - const OccupancyCalculator::Result occ = occupancyCalc.calculateForSource(colIndex); + const OccupancyCalculator::Result occ = occupancyCalc.calculateForCollision(colIndex); // compare zVtx from FT0 and from PV bool isGoodZvtxFT0vsPV = 0; @@ -1652,11 +1706,13 @@ class EventSelectionModule const int bcsForOccupancy = bcoccOpts.bcsForOccupancy.value; for (const auto& bc : bcs) { if (!isOccupancyInputReady) { - bcocc(-1, -1.f, 0.f, 0, 0); + bcocc(-1, -1.f, 0.f, 0, 0, 0); continue; } int64_t globalBC = bc.globalBC(); - const auto nCollsInBC = static_cast(std::min(occupancyCalc.numSourcesInBC(globalBC), static_cast(std::numeric_limits::max()))); + const auto nCollsInBC = static_cast(std::min(occupancyCalc.numCollisionsInBC(globalBC), static_cast(std::numeric_limits::max()))); + const auto nCollsInRof = static_cast(std::min(occupancyCalc.numCollisionsInRof(globalBC), static_cast(std::numeric_limits::max()))); + const auto upcModeFlag = static_cast(occupancyCalc.isRofInUpcMode(globalBC) ? BIT(aod::bcocc::kUpcMode) : 0); // set for all BCs // BCs for which the occupancy is calculated bool isSelected = true; @@ -1669,12 +1725,12 @@ class EventSelectionModule isSelected = bcPatternB[globalBC % nBCsPerOrbit]; } if (!isSelected) { - bcocc(-1, -1.f, 0.f, 0, nCollsInBC); + bcocc(-1, -1.f, 0.f, upcModeFlag, nCollsInBC, nCollsInRof); continue; } const OccupancyCalculator::Result occ = occupancyCalc.calculateForBC(globalBC); - bcocc(occ.nTracksITS567inTimeWin, occ.sumAmpFT0CinTimeWin, occ.medianTime, getBcOccupancyFlags(occ), nCollsInBC); + bcocc(occ.nTracksITS567inTimeWin, occ.sumAmpFT0CinTimeWin, occ.medianTime, static_cast(getBcOccupancyFlags(occ) | upcModeFlag), nCollsInBC, nCollsInRof); } } // end processRun3BcOccupancy @@ -1715,18 +1771,20 @@ class EventSelectionModule for (const auto& tfId : tfIds) { const int64_t tfStartBC = bcSOR + tfId * nBCsPerTF; for (int64_t globalBC = tfStartBC; globalBC < tfStartBC + nBCsPerTF; globalBC++) { - const auto nCollsInBC = static_cast(std::min(occupancyCalc.numSourcesInBC(globalBC), static_cast(std::numeric_limits::max()))); + const auto nCollsInBC = static_cast(std::min(occupancyCalc.numCollisionsInBC(globalBC), static_cast(std::numeric_limits::max()))); + const auto nCollsInRof = static_cast(std::min(occupancyCalc.numCollisionsInRof(globalBC), static_cast(std::numeric_limits::max()))); + const auto upcModeFlag = static_cast(occupancyCalc.isRofInUpcMode(globalBC) ? BIT(aod::bcocc::kUpcMode) : 0); // set for all BCs if (collidingBCsOnly && !bcPatternB[globalBC % nBCsPerOrbit]) { - bcoccall(static_cast(globalBC), -1, -1.f, 0.f, 0, nCollsInBC); + bcoccall(static_cast(globalBC), -1, -1.f, 0.f, upcModeFlag, nCollsInBC, nCollsInRof); continue; } const OccupancyCalculator::Result occ = occupancyCalc.calculateForBC(globalBC); - bcoccall(static_cast(globalBC), occ.nTracksITS567inTimeWin, occ.sumAmpFT0CinTimeWin, occ.medianTime, getBcOccupancyFlags(occ), nCollsInBC); + bcoccall(static_cast(globalBC), occ.nTracksITS567inTimeWin, occ.sumAmpFT0CinTimeWin, occ.medianTime, static_cast(getBcOccupancyFlags(occ) | upcModeFlag), nCollsInBC, nCollsInRof); } } } // end processRun3BcOccupancyAll - // flags of aod::BcOccs and aod::BcOccsAll (see aod::bcocc::BcOccupancyFlags) + // occupancy flags of aod::BcOccs and aod::BcOccsAll (see aod::bcocc::BcOccupancyFlags; kUpcMode is set separately) static uint8_t getBcOccupancyFlags(const OccupancyCalculator::Result& occ) { // note: BcOccupancyFlags must fit in the 8-bit bitmap From 2a4802b4c312f44a37b26a4b40f192cfea0a821f Mon Sep 17 00:00:00 2001 From: Igor Altsybeev Date: Wed, 11 Nov 2026 15:54:39 +0100 Subject: [PATCH 10/14] add qa for bcOcc table --- DPG/Tasks/AOTEvent/bcOccupancyQa.cxx | 106 ++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 4 deletions(-) diff --git a/DPG/Tasks/AOTEvent/bcOccupancyQa.cxx b/DPG/Tasks/AOTEvent/bcOccupancyQa.cxx index f1a6f017dae..88717aba230 100644 --- a/DPG/Tasks/AOTEvent/bcOccupancyQa.cxx +++ b/DPG/Tasks/AOTEvent/bcOccupancyQa.cxx @@ -40,16 +40,17 @@ struct BcOccupancyQaTask { Configurable maxOccupancyByTracks{"maxOccupancyByTracks", 25000., "upper edge of the axis for the occupancy by ITS tracks"}; Configurable nBinsOccupancyByFT0C{"nBinsOccupancyByFT0C", 500, "number of bins for the occupancy by FT0C amplitudes"}; Configurable maxOccupancyByFT0C{"maxOccupancyByFT0C", 250000., "upper edge of the axis for the occupancy by FT0C amplitudes"}; + Configurable nBinsNumCollsInRof{"nBinsNumCollsInRof", 50, "number of bins for the number of collisions in the ITS ROF (also the upper edge of the axis)"}; Configurable nBinsBcInTF{"nBinsBcInTF", 1188, "number of bins for the BC position in the TF"}; Configurable nOrbitsPerTF{"nOrbitsPerTF", 32, "number of orbits per TF, for the histograms vs BC position in the TF (128 in 2022)"}; HistogramRegistry histos{"histos", {}, OutputObjHandlingPolicy::AnalysisObject}; - static constexpr int NFlags = 6; + static constexpr int NFlags = 7; static constexpr std::array FlagBits = {aod::bcocc::kNoCollInTimeRangeNarrow, aod::bcocc::kNoCollInTimeRangeStrict, aod::bcocc::kNoCollInTimeRangeStandard, - aod::bcocc::kNoCollInRofStrict, aod::bcocc::kNoCollInRofStandard, aod::bcocc::kNoHighMultCollInPrevRof}; + aod::bcocc::kNoCollInRofStrict, aod::bcocc::kNoCollInRofStandard, aod::bcocc::kNoHighMultCollInPrevRof, aod::bcocc::kUpcMode}; static constexpr std::array FlagNames = {"NoCollInTimeRangeNarrow", "NoCollInTimeRangeStrict", "NoCollInTimeRangeStandard", - "NoCollInRofStrict", "NoCollInRofStandard", "NoHighMultCollInPrevRof"}; + "NoCollInRofStrict", "NoCollInRofStandard", "NoHighMultCollInPrevRof", "UpcMode"}; static constexpr int NBCsPerOrbit = 3564; int64_t nBCsPerTF = 0; @@ -60,6 +61,7 @@ struct BcOccupancyQaTask { const AxisSpec axisOccupancyByFT0C{nBinsOccupancyByFT0C.value, 0., maxOccupancyByFT0C.value, "occupancy by FT0C amplitudes"}; const AxisSpec axisMedianTime{280, -40., 100., "median time of the occupancy (#mus)"}; const AxisSpec axisNumCollsInBC{11, -0.5, 10.5, "number of collisions found in the BC"}; + const AxisSpec axisNumCollsInRof{nBinsNumCollsInRof.value + 1, -1.5, nBinsNumCollsInRof.value - 0.5, "number of collisions found in the ITS ROF of the BC"}; const AxisSpec axisBcInTF{nBinsBcInTF.value, 0., static_cast(nBCsPerTF), "BC in TF"}; const AxisSpec axisBcInOrbit{NBCsPerOrbit, -0.5, NBCsPerOrbit - 0.5, "BC in orbit"}; const AxisSpec axisFlags{NFlags + 1, -0.5, NFlags + 0.5, ""}; @@ -69,6 +71,9 @@ struct BcOccupancyQaTask { // all BCs histos.add("hBcCounter", "number of BCs", kTH1D, {axisBcCounter}); histos.add("hNumCollsInBC", "all BCs", kTH1D, {axisNumCollsInBC}); + histos.add("hNumCollsInRof", "all BCs", kTH1D, {axisNumCollsInRof}); + histos.add("hNumCollsInRofVsInBC", "all BCs", kTH2D, {axisNumCollsInBC, axisNumCollsInRof}); + histos.add("hNumCollsInRofUpcMode", "all BCs in ITS ROFs in UPC mode (kUpcMode)", kTH1D, {axisNumCollsInRof}); histos.add("hBcInTF", "all BCs", kTH1D, {axisBcInTF}); // BCs with calculated occupancy histos.add("hBcInTFOccupancyDefined", "BCs with calculated occupancy", kTH1D, {axisBcInTF}); @@ -103,9 +108,39 @@ struct BcOccupancyQaTask { hBcIndexCheck->GetXaxis()->SetBinLabel(4, "BC found in BcOccsAll"); hBcIndexCheck->GetXaxis()->SetBinLabel(5, "BC found, same global BC"); hBcIndexCheck->GetXaxis()->SetBinLabel(6, "BC not found"); + + // aod::BcOccs (joinable with aod::BCs) + histos.add("BcOccs/hBcCounter", "number of BCs", kTH1D, {axisBcCounter}); + histos.add("BcOccs/hNumCollsInBC", "all BCs", kTH1D, {axisNumCollsInBC}); + histos.add("BcOccs/hNumCollsInRof", "all BCs", kTH1D, {axisNumCollsInRof}); + histos.add("BcOccs/hOccupancyByTracks", "BCs with calculated occupancy", kTH1D, {axisOccupancyByTracks}); + histos.add("BcOccs/hOccupancyByFT0C", "BCs with calculated occupancy", kTH1D, {axisOccupancyByFT0C}); + histos.add("BcOccs/hOccupancyByTracksVsFT0C", "BCs with calculated occupancy", kTH2D, {axisOccupancyByFT0C, axisOccupancyByTracks}); + histos.add("BcOccs/hOccupancyFlags", "BCs with calculated occupancy", kTH1D, {axisFlags}); + auto hBcCounterBcOccs = histos.get(HIST("BcOccs/hBcCounter")); + hBcCounterBcOccs->GetXaxis()->SetBinLabel(1, "all"); + hBcCounterBcOccs->GetXaxis()->SetBinLabel(2, "occupancy calculated"); + hBcCounterBcOccs->GetXaxis()->SetBinLabel(3, "occupancy not available"); + auto hFlagsBcOccs = histos.get(HIST("BcOccs/hOccupancyFlags")); + hFlagsBcOccs->GetXaxis()->SetBinLabel(1, "all"); + for (int i = 0; i < NFlags; i++) { + hFlagsBcOccs->GetXaxis()->SetBinLabel(i + 2, FlagNames[i]); + } + + // comparison of aod::BcOccs with aod::BcOccsAll for the BCs of aod::BCs + histos.add("BcOccsVsBcOccsAll/hCheck", "BcOccs vs BcOccsAll", kTH1D, {{7, -0.5, 6.5, ""}}); + auto hCheck = histos.get(HIST("BcOccsVsBcOccsAll/hCheck")); + hCheck->GetXaxis()->SetBinLabel(1, "BCs in BcOccs"); + hCheck->GetXaxis()->SetBinLabel(2, "found in BcOccsAll"); + hCheck->GetXaxis()->SetBinLabel(3, "same numCollsInBC, numCollsInRof"); + hCheck->GetXaxis()->SetBinLabel(4, "same kUpcMode"); + hCheck->GetXaxis()->SetBinLabel(5, "occupancy calculated in both"); + hCheck->GetXaxis()->SetBinLabel(6, "same occupancy and flags"); + hCheck->GetXaxis()->SetBinLabel(7, "not found in BcOccsAll"); } - void process(aod::BcOccsAll const& bcOccs, aod::BCs const& bcs) + // QA of aod::BcOccsAll (all BCs of the TFs), with the cross-check of the lookup by global BC + void processBcOccsAll(aod::BcOccsAll const& bcOccs, aod::BCs const& bcs) { // cross-check of the lookup by global BC: every BC of the BC table should be found, with the same global BC // (not found: BCs before the start of run, or no BcOccsAll entries for this DF) @@ -140,6 +175,11 @@ struct BcOccupancyQaTask { const int bcInOrbit = static_cast(globalBC % NBCsPerOrbit); histos.fill(HIST("hBcCounter"), 0); histos.fill(HIST("hNumCollsInBC"), bcOcc.numCollsInBC()); + histos.fill(HIST("hNumCollsInRof"), bcOcc.numCollsInRof()); + histos.fill(HIST("hNumCollsInRofVsInBC"), bcOcc.numCollsInBC(), bcOcc.numCollsInRof()); + if (bcOcc.occupancyFlags_bit(aod::bcocc::kUpcMode)) { // set for all BCs, also without calculated occupancy + histos.fill(HIST("hNumCollsInRofUpcMode"), bcOcc.numCollsInRof()); + } histos.fill(HIST("hBcIndexCheck"), 0); if (bcOccAllIndexed.contains(globalBC) && bcOccAllIndexed[globalBC].globalBC() == globalBC) { // global BCs are unique: same row histos.fill(HIST("hBcIndexCheck"), 1); @@ -176,6 +216,64 @@ struct BcOccupancyQaTask { } } } + PROCESS_SWITCH(BcOccupancyQaTask, processBcOccsAll, "QA of aod::BcOccsAll (all BCs of the TFs)", true); + + // QA of aod::BcOccs (joinable with aod::BCs) + void processBcOccs(soa::Join const& bcs) + { + for (const auto& bc : bcs) { + histos.fill(HIST("BcOccs/hBcCounter"), 0); + histos.fill(HIST("BcOccs/hNumCollsInBC"), bc.numCollsInBC()); + histos.fill(HIST("BcOccs/hNumCollsInRof"), bc.numCollsInRof()); + const int occByTracks = bc.trackOccupancyInTimeRange(); + const float occByFT0C = bc.ft0cOccupancyInTimeRange(); + if (occByTracks < 0 || occByFT0C < 0) { + histos.fill(HIST("BcOccs/hBcCounter"), 2); + continue; + } + histos.fill(HIST("BcOccs/hBcCounter"), 1); + histos.fill(HIST("BcOccs/hOccupancyByTracks"), occByTracks); + histos.fill(HIST("BcOccs/hOccupancyByFT0C"), occByFT0C); + histos.fill(HIST("BcOccs/hOccupancyByTracksVsFT0C"), occByFT0C, occByTracks); + histos.fill(HIST("BcOccs/hOccupancyFlags"), 0); + for (int i = 0; i < NFlags; i++) { + if (bc.occupancyFlags_bit(FlagBits[i])) { + histos.fill(HIST("BcOccs/hOccupancyFlags"), i + 1); + } + } + } + } + PROCESS_SWITCH(BcOccupancyQaTask, processBcOccs, "QA of aod::BcOccs (joinable with aod::BCs)", true); + + // comparison of aod::BcOccs with aod::BcOccsAll for the BCs of aod::BCs: the same calculation, so the values should be identical + // (occupancy and flags are compared only if calculated in both, since bcsForOccupancy can differ for the two tables) + void processBcOccsVsBcOccsAll(soa::Join const& bcs, aod::BcOccsAll const& bcOccsAll) + { + aod::bcocc::BcOccAllIndex bcOccAllIndexed(bcOccsAll); + for (const auto& bc : bcs) { + histos.fill(HIST("BcOccsVsBcOccsAll/hCheck"), 0); + if (!bcOccAllIndexed.contains(bc.globalBC())) { + histos.fill(HIST("BcOccsVsBcOccsAll/hCheck"), 6); + continue; + } + histos.fill(HIST("BcOccsVsBcOccsAll/hCheck"), 1); + const auto occAll = bcOccAllIndexed[bc.globalBC()]; + if (bc.numCollsInBC() == occAll.numCollsInBC() && bc.numCollsInRof() == occAll.numCollsInRof()) { + histos.fill(HIST("BcOccsVsBcOccsAll/hCheck"), 2); + } + if (bc.occupancyFlags_bit(aod::bcocc::kUpcMode) == occAll.occupancyFlags_bit(aod::bcocc::kUpcMode)) { + histos.fill(HIST("BcOccsVsBcOccsAll/hCheck"), 3); + } + if (bc.trackOccupancyInTimeRange() >= 0 && occAll.trackOccupancyInTimeRange() >= 0) { + histos.fill(HIST("BcOccsVsBcOccsAll/hCheck"), 4); + if (bc.trackOccupancyInTimeRange() == occAll.trackOccupancyInTimeRange() && bc.ft0cOccupancyInTimeRange() == occAll.ft0cOccupancyInTimeRange() && + bc.occupancyMedianTime() == occAll.occupancyMedianTime() && bc.occupancyFlags() == occAll.occupancyFlags()) { + histos.fill(HIST("BcOccsVsBcOccsAll/hCheck"), 5); + } + } + } + } + PROCESS_SWITCH(BcOccupancyQaTask, processBcOccsVsBcOccsAll, "comparison of aod::BcOccs with aod::BcOccsAll for the BCs of aod::BCs (requires both tables)", true); }; WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) From 2c4671668999f2799e22b9ba0d8d6f2d3d57cf17 Mon Sep 17 00:00:00 2001 From: Igor Altsybeev Date: Wed, 11 Nov 2026 15:55:04 +0100 Subject: [PATCH 11/14] updated qa plots --- DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C | 108 +++++++++++++++++- 1 file changed, 103 insertions(+), 5 deletions(-) diff --git a/DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C b/DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C index 9f9049d6d6e..fd22307683a 100644 --- a/DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C +++ b/DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C @@ -61,7 +61,10 @@ T* getHist(const char* name) return nullptr; } h->SetDirectory(nullptr); - h->SetStats(false); // also when the saved canvases are drawn later + h->SetStats(false); // also when the saved canvases are drawn later + TString uniqueName = name; // histograms from subdirectories (e.g. "BcOccs/hBcCounter"): unique names in the output file + uniqueName.ReplaceAll("/", "_"); + h->SetName(uniqueName); return h; } @@ -109,6 +112,9 @@ void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char auto* hBcCounter = getHist("hBcCounter"); auto* hNumCollsInBC = getHist("hNumCollsInBC"); + auto* hNumCollsInRof = getHist("hNumCollsInRof"); + auto* hNumCollsInRofVsInBC = getHist("hNumCollsInRofVsInBC"); + auto* hNumCollsInRofUpcMode = getHist("hNumCollsInRofUpcMode"); auto* hBcInTF = getHist("hBcInTF"); auto* hBcInTFDefined = getHist("hBcInTFOccupancyDefined"); auto* hOccTracks = getHist("hOccupancyByTracks"); @@ -120,6 +126,12 @@ void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char auto* hFlags = getHist("hOccupancyFlags"); auto* hFlagsVsOcc = getHist("hOccupancyFlagsVsOccupancyByTracks"); auto* hBcIndexCheck = getHist("hBcIndexCheck"); + // aod::BcOccs, and its comparison with aod::BcOccsAll + auto* hBcOccsCounter = getHist("BcOccs/hBcCounter"); + auto* hBcOccsOccTracks = getHist("BcOccs/hOccupancyByTracks"); + auto* hBcOccsOccTracksVsFT0C = getHist("BcOccs/hOccupancyByTracksVsFT0C"); + auto* hBcOccsFlags = getHist("BcOccs/hOccupancyFlags"); + auto* hBcOccsVsBcOccsAll = getHist("BcOccsVsBcOccsAll/hCheck"); // ### summary std::printf("\n=== BC occupancy QA: %s ===\n", fileName); @@ -136,6 +148,15 @@ void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char nAll > 0 ? 100. * hNumCollsInBC->Integral(hNumCollsInBC->FindBin(1), hNumCollsInBC->GetNbinsX() + 1) / nAll : 0., nAll > 0 ? 100. * hNumCollsInBC->Integral(hNumCollsInBC->FindBin(2), hNumCollsInBC->GetNbinsX() + 1) / nAll : 0.); } + if (hNumCollsInRof && hNumCollsInRof->GetEntries() > 0) { + const double nAll = hNumCollsInRof->GetEntries(); + const double nNotAvailable = hNumCollsInRof->GetBinContent(hNumCollsInRof->FindBin(-1)); + std::printf("collisions in the ITS ROF of a BC: mean %.3f, BCs in ROFs with >= 2 collisions: %.2f%%, not available (-1): %.0f\n", + hNumCollsInRof->GetMean(), 100. * hNumCollsInRof->Integral(hNumCollsInRof->FindBin(2), hNumCollsInRof->GetNbinsX() + 1) / nAll, nNotAvailable); + if (hNumCollsInRofUpcMode) { + std::printf("BCs in ITS ROFs in UPC mode (kUpcMode): %.2f%%\n", 100. * hNumCollsInRofUpcMode->GetEntries() / nAll); + } + } if (hFlags && hFlags->GetBinContent(1) > 0) { std::printf("fraction of BCs (with calculated occupancy) passing each flag:\n"); for (int i = 2; i <= hFlags->GetNbinsX(); i++) { @@ -151,6 +172,18 @@ void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char hBcIndexCheck->GetBinContent(6), hBcIndexCheck->GetBinContent(5), ok ? "OK" : "MISMATCH"); } + if (hBcOccsCounter && hBcOccsCounter->GetBinContent(1) > 0) { + const double nAll = hBcOccsCounter->GetBinContent(1), nDef = hBcOccsCounter->GetBinContent(2); + std::printf("BcOccs: BCs %.0f, with calculated occupancy: %.0f (%.2f%%)\n", nAll, nDef, 100. * nDef / nAll); + } + if (hBcOccsVsBcOccsAll && hBcOccsVsBcOccsAll->GetBinContent(1) > 0) { + // all BCs found in BcOccsAll should have identical values + const auto bin = [hBcOccsVsBcOccsAll](int i) { return hBcOccsVsBcOccsAll->GetBinContent(i); }; + const bool ok = bin(3) == bin(2) && bin(4) == bin(2) && bin(6) == bin(5); + std::printf("BcOccs vs BcOccsAll: BCs found %.0f / %.0f (not found %.0f), same collision counts %.0f, same kUpcMode %.0f, same occupancy and flags %.0f / %.0f -> %s\n", + bin(2), bin(1), bin(7), bin(3), bin(4), bin(6), bin(5), ok ? "OK" : "MISMATCH"); + } + // ### plots const double maxOccTracks = hOccTracks ? upperEdgeOfFilledRange(hOccTracks) : 0.; const double maxOccFT0C = hOccFT0C ? upperEdgeOfFilledRange(hOccFT0C) : 0.; @@ -165,8 +198,8 @@ void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char }; TCanvas* c = nullptr; - // page 1: BC counters and the cross-check of the lookup by global BC - c = newPage("cBcCounters", "BC counters", 1500, 550, 3, 1); + // page 1: BC counters, collisions in the BC and in the ITS ROF, and the cross-check of the lookup by global BC + c = newPage("cBcCounters", "BC counters", 1500, 1000, 3, 2); if (hBcCounter) { c->cd(1)->SetLeftMargin(0.15); hBcCounter->SetTitle("number of BCs"); @@ -185,6 +218,28 @@ void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char hBcIndexCheck->SetMinimum(0.5); hBcIndexCheck->Draw("hist text"); } + const double maxNumCollsInRof = hNumCollsInRof ? upperEdgeOfFilledRange(hNumCollsInRof) + 1. : 0.; + if (hNumCollsInRof) { + c->cd(4)->SetLogy(); + c->cd(4)->SetLeftMargin(0.15); + hNumCollsInRof->SetTitle("all BCs;number of collisions in the ITS ROF of the BC;N BCs"); + hNumCollsInRof->GetXaxis()->SetRangeUser(-1.5, maxNumCollsInRof); + hNumCollsInRof->Draw("hist"); + if (hNumCollsInRofUpcMode) { + hNumCollsInRofUpcMode->SetLineColor(kRed + 1); + hNumCollsInRofUpcMode->Draw("hist same"); + auto* legRof = new TLegend(0.5, 0.75, 0.89, 0.89); + legRof->AddEntry(hNumCollsInRof, "all BCs", "l"); + legRof->AddEntry(hNumCollsInRofUpcMode, "BCs in UPC-mode ROFs", "l"); + legRof->Draw(); + } + } + if (hNumCollsInRofVsInBC) { + c->cd(5)->SetLogz(); + hNumCollsInRofVsInBC->SetTitle("all BCs;number of collisions in the BC;number of collisions in the ITS ROF of the BC"); + hNumCollsInRofVsInBC->GetYaxis()->SetRangeUser(-1.5, maxNumCollsInRof); + hNumCollsInRofVsInBC->Draw("colz"); + } // page 2: occupancy distributions c = newPage("cOccupancy", "occupancy distributions", 1200, 800, 2, 2); @@ -272,6 +327,48 @@ void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char pOccVsBcInOrbit->Draw("hist"); // 3564 bins: error bars would hide the pattern } + // page 5: aod::BcOccs and its comparison with aod::BcOccsAll + c = newPage("cBcOccs", "BcOccs and comparison with BcOccsAll", 1500, 1000, 3, 2); + if (hBcOccsCounter) { + c->cd(1)->SetLeftMargin(0.15); + hBcOccsCounter->SetTitle("BcOccs: number of BCs"); + hBcOccsCounter->Draw("hist text"); + } + if (hBcOccsFlags && hBcOccsFlags->GetBinContent(1) > 0) { + c->cd(2)->SetBottomMargin(0.3); + auto* hFlagsFracBcOccs = static_cast(hBcOccsFlags->Clone("BcOccs_hFlagsFrac")); + hFlagsFracBcOccs->Scale(1. / hBcOccsFlags->GetBinContent(1)); + hFlagsFracBcOccs->SetTitle("BcOccs: fraction of BCs passing each flag;;fraction of BCs"); + hFlagsFracBcOccs->GetXaxis()->LabelsOption("v"); + hFlagsFracBcOccs->SetMinimum(0.); + hFlagsFracBcOccs->SetMaximum(1.1); + hFlagsFracBcOccs->Draw("hist text"); + derivedHists.push_back(hFlagsFracBcOccs); + } + if (hBcOccsVsBcOccsAll) { + c->cd(3)->SetBottomMargin(0.35); + c->cd(3)->SetLogy(); + hBcOccsVsBcOccsAll->SetTitle("BcOccs vs BcOccsAll (BCs of aod::BCs);;counts"); + hBcOccsVsBcOccsAll->GetXaxis()->LabelsOption("v"); + hBcOccsVsBcOccsAll->SetMinimum(0.5); + hBcOccsVsBcOccsAll->Draw("hist text"); + } + if (hBcOccsOccTracks) { + c->cd(4)->SetLeftMargin(0.15); + c->cd(4)->SetLogy(); + hBcOccsOccTracks->SetTitle("BcOccs: occupancy by ITS tracks;occupancy by ITS tracks;N BCs"); + hBcOccsOccTracks->GetXaxis()->SetRangeUser(0., maxOccTracks); + hBcOccsOccTracks->Draw("hist"); + } + if (hBcOccsOccTracksVsFT0C) { + c->cd(5)->SetLeftMargin(0.15); + c->cd(5)->SetLogz(); + hBcOccsOccTracksVsFT0C->SetTitle("BcOccs: occupancy, ITS tracks vs FT0C;occupancy by FT0C amplitudes;occupancy by ITS tracks"); + hBcOccsOccTracksVsFT0C->GetXaxis()->SetRangeUser(0., maxOccFT0C); + hBcOccsOccTracksVsFT0C->GetYaxis()->SetRangeUser(0., maxOccTracks); + hBcOccsOccTracksVsFT0C->Draw("colz"); + } + // PDF: each page with the aspect ratio of its canvas for (size_t i = 0; i < canvases.size(); i++) { gStyle->SetPaperSize(30., 30. * canvases[i]->GetWh() / canvases[i]->GetWw()); @@ -304,8 +401,9 @@ void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char canvas->Write(); } fOut->mkdir("histograms")->cd(); - for (TH1* h : {static_cast(hBcCounter), hNumCollsInBC, hBcInTF, hBcInTFDefined, hOccTracks, hOccFT0C, static_cast(hOccTracksVsFT0C), hMedianTime, - static_cast(pOccVsBcInTF), static_cast(pOccVsBcInOrbit), hFlags, static_cast(hFlagsVsOcc), hBcIndexCheck}) { + for (TH1* h : {static_cast(hBcCounter), hNumCollsInBC, hNumCollsInRof, static_cast(hNumCollsInRofVsInBC), hNumCollsInRofUpcMode, hBcInTF, hBcInTFDefined, hOccTracks, hOccFT0C, static_cast(hOccTracksVsFT0C), hMedianTime, + static_cast(pOccVsBcInTF), static_cast(pOccVsBcInOrbit), hFlags, static_cast(hFlagsVsOcc), hBcIndexCheck, + hBcOccsCounter, hBcOccsOccTracks, static_cast(hBcOccsOccTracksVsFT0C), hBcOccsFlags, hBcOccsVsBcOccsAll}) { if (h) { h->Write(); } From 0c5d342e8f98cbc695f133a7af76f4266840198b Mon Sep 17 00:00:00 2001 From: Igor Altsybeev Date: Wed, 11 Nov 2026 17:41:18 +0100 Subject: [PATCH 12/14] fix occupancy flag comparison --- DPG/Tasks/AOTEvent/bcOccupancyQa.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DPG/Tasks/AOTEvent/bcOccupancyQa.cxx b/DPG/Tasks/AOTEvent/bcOccupancyQa.cxx index 88717aba230..4f1d0d555c8 100644 --- a/DPG/Tasks/AOTEvent/bcOccupancyQa.cxx +++ b/DPG/Tasks/AOTEvent/bcOccupancyQa.cxx @@ -164,7 +164,7 @@ struct BcOccupancyQaTask { // so bcInTF = (globalBC - globalBC of the first row) % nBCsPerTF; check that nOrbitsPerTF is consistent with the table const uint64_t firstGlobalBC = bcOccs.iteratorAt(0).globalBC(); const uint64_t lastGlobalBC = bcOccs.iteratorAt(bcOccs.size() - 1).globalBC(); - const bool isBcInTFValid = (static_cast(bcOccs.size()) % nBCsPerTF == 0) && (static_cast((lastGlobalBC - firstGlobalBC) % nBCsPerTF) == nBCsPerTF - 1); + const bool isBcInTFValid = (bcOccs.size() % nBCsPerTF == 0) && (static_cast((lastGlobalBC - firstGlobalBC) % nBCsPerTF) == nBCsPerTF - 1); if (!isBcInTFValid) { LOGP(warning, "BC occupancy QA: table size {} is not consistent with {} orbits per TF, no histograms vs BC in TF for this DF", bcOccs.size(), nOrbitsPerTF.value); } @@ -267,7 +267,7 @@ struct BcOccupancyQaTask { if (bc.trackOccupancyInTimeRange() >= 0 && occAll.trackOccupancyInTimeRange() >= 0) { histos.fill(HIST("BcOccsVsBcOccsAll/hCheck"), 4); if (bc.trackOccupancyInTimeRange() == occAll.trackOccupancyInTimeRange() && bc.ft0cOccupancyInTimeRange() == occAll.ft0cOccupancyInTimeRange() && - bc.occupancyMedianTime() == occAll.occupancyMedianTime() && bc.occupancyFlags() == occAll.occupancyFlags()) { + bc.occupancyMedianTime() == occAll.occupancyMedianTime() && bc.occupancyFlags_raw() == occAll.occupancyFlags_raw()) { histos.fill(HIST("BcOccsVsBcOccsAll/hCheck"), 5); } } From 0574397ff95ecf1255b4e3216da0758e45450011 Mon Sep 17 00:00:00 2001 From: Igor Altsybeev Date: Wed, 11 Nov 2026 17:41:57 +0100 Subject: [PATCH 13/14] fixes of headers --- DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C | 83 ++++++++++--------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C b/DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C index fd22307683a..c5bd175b986 100644 --- a/DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C +++ b/DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C @@ -20,6 +20,7 @@ /// \author Igor Altsybeev #include +#include #include #include #include @@ -30,24 +31,28 @@ #include #include +#include + #include +#include #include #include -#include #include namespace bcoccqa { -TDirectory* gDir = nullptr; - // histogram from the task directory (or from one of its subdirectories), detached from the file template -T* getHist(const char* name) +T* getHist(TDirectory* dir, const char* name) { - TObject* obj = gDir->Get(name); + TObject* obj = dir->Get(name); if (!obj) { - for (TObject* key : *gDir->GetListOfKeys()) { - if (auto* sub = dynamic_cast(static_cast(key)->ReadObj())) { + for (TObject* key : *dir->GetListOfKeys()) { + auto* tkey = dynamic_cast(key); + if (!tkey) { + continue; + } + if (auto* sub = dynamic_cast(tkey->ReadObj())) { obj = sub->Get(name); if (obj) { break; @@ -69,9 +74,9 @@ T* getHist(const char* name) } // fraction of BCs per bin: numerator / denominator (binomial errors) -TH1D* makeRatio(const TH1* num, const TH1* den, const char* name, const char* title) +TH1* makeRatio(const TH1* num, const TH1* den, const char* name, const char* title) { - auto* r = static_cast(num->Clone(name)); + auto* r = dynamic_cast(num->Clone(name)); r->SetTitle(title); r->Divide(num, den, 1., 1., "B"); r->SetStats(false); @@ -95,7 +100,7 @@ double upperEdgeOfFilledRange(const TH1* h) void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char* outPdf = "bcOccupancyQa.pdf", const char* dirName = "bc-occupancy-qa-task", const char* outRoot = "") { using namespace bcoccqa; - TH1::AddDirectory(kFALSE); + TH1::AddDirectory(false); gStyle->SetOptStat(0); gStyle->SetPalette(kViridis); @@ -104,34 +109,34 @@ void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char std::printf("cannot open %s\n", fileName); return; } - gDir = f->GetDirectory(dirName); - if (!gDir) { + TDirectory* dir = f->GetDirectory(dirName); + if (!dir) { std::printf("directory %s not found in %s\n", dirName, fileName); return; } - auto* hBcCounter = getHist("hBcCounter"); - auto* hNumCollsInBC = getHist("hNumCollsInBC"); - auto* hNumCollsInRof = getHist("hNumCollsInRof"); - auto* hNumCollsInRofVsInBC = getHist("hNumCollsInRofVsInBC"); - auto* hNumCollsInRofUpcMode = getHist("hNumCollsInRofUpcMode"); - auto* hBcInTF = getHist("hBcInTF"); - auto* hBcInTFDefined = getHist("hBcInTFOccupancyDefined"); - auto* hOccTracks = getHist("hOccupancyByTracks"); - auto* hOccFT0C = getHist("hOccupancyByFT0C"); - auto* hOccTracksVsFT0C = getHist("hOccupancyByTracksVsFT0C"); - auto* hMedianTime = getHist("hMedianTime"); - auto* pOccVsBcInTF = getHist("pOccupancyByTracksVsBcInTF"); - auto* pOccVsBcInOrbit = getHist("pOccupancyByTracksVsBcInOrbit"); - auto* hFlags = getHist("hOccupancyFlags"); - auto* hFlagsVsOcc = getHist("hOccupancyFlagsVsOccupancyByTracks"); - auto* hBcIndexCheck = getHist("hBcIndexCheck"); + auto* hBcCounter = getHist(dir, "hBcCounter"); + auto* hNumCollsInBC = getHist(dir, "hNumCollsInBC"); + auto* hNumCollsInRof = getHist(dir, "hNumCollsInRof"); + auto* hNumCollsInRofVsInBC = getHist(dir, "hNumCollsInRofVsInBC"); + auto* hNumCollsInRofUpcMode = getHist(dir, "hNumCollsInRofUpcMode"); + auto* hBcInTF = getHist(dir, "hBcInTF"); + auto* hBcInTFDefined = getHist(dir, "hBcInTFOccupancyDefined"); + auto* hOccTracks = getHist(dir, "hOccupancyByTracks"); + auto* hOccFT0C = getHist(dir, "hOccupancyByFT0C"); + auto* hOccTracksVsFT0C = getHist(dir, "hOccupancyByTracksVsFT0C"); + auto* hMedianTime = getHist(dir, "hMedianTime"); + auto* pOccVsBcInTF = getHist(dir, "pOccupancyByTracksVsBcInTF"); + auto* pOccVsBcInOrbit = getHist(dir, "pOccupancyByTracksVsBcInOrbit"); + auto* hFlags = getHist(dir, "hOccupancyFlags"); + auto* hFlagsVsOcc = getHist(dir, "hOccupancyFlagsVsOccupancyByTracks"); + auto* hBcIndexCheck = getHist(dir, "hBcIndexCheck"); // aod::BcOccs, and its comparison with aod::BcOccsAll - auto* hBcOccsCounter = getHist("BcOccs/hBcCounter"); - auto* hBcOccsOccTracks = getHist("BcOccs/hOccupancyByTracks"); - auto* hBcOccsOccTracksVsFT0C = getHist("BcOccs/hOccupancyByTracksVsFT0C"); - auto* hBcOccsFlags = getHist("BcOccs/hOccupancyFlags"); - auto* hBcOccsVsBcOccsAll = getHist("BcOccsVsBcOccsAll/hCheck"); + auto* hBcOccsCounter = getHist(dir, "BcOccs/hBcCounter"); + auto* hBcOccsOccTracks = getHist(dir, "BcOccs/hOccupancyByTracks"); + auto* hBcOccsOccTracksVsFT0C = getHist(dir, "BcOccs/hOccupancyByTracksVsFT0C"); + auto* hBcOccsFlags = getHist(dir, "BcOccs/hOccupancyFlags"); + auto* hBcOccsVsBcOccsAll = getHist(dir, "BcOccsVsBcOccsAll/hCheck"); // ### summary std::printf("\n=== BC occupancy QA: %s ===\n", fileName); @@ -272,7 +277,7 @@ void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char c = newPage("cOccupancyFlags", "occupancy flags", 1200, 550, 2, 1); if (hFlags && hFlags->GetBinContent(1) > 0) { c->cd(1)->SetBottomMargin(0.3); - auto* hFlagsFrac = static_cast(hFlags->Clone("hFlagsFrac")); + auto* hFlagsFrac = dynamic_cast(hFlags->Clone("hFlagsFrac")); hFlagsFrac->Scale(1. / hFlags->GetBinContent(1)); hFlagsFrac->SetTitle("fraction of BCs passing each flag;;fraction of BCs"); hFlagsFrac->GetXaxis()->LabelsOption("v"); @@ -284,13 +289,13 @@ void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char if (hFlagsVsOcc) { // fraction of BCs passing each flag vs occupancy c->cd(2); - TLegend* leg = new TLegend(0.45, 0.6, 0.89, 0.89); + auto* leg = new TLegend(0.45, 0.6, 0.89, 0.89); TH1D* hAll = hFlagsVsOcc->ProjectionY("hFlagsAll", 1, 1); - const int colors[] = {kRed + 1, kOrange + 7, kGreen + 2, kAzure + 2, kBlue + 2, kMagenta + 1, kBlack}; + const std::array colors = {kRed + 1, kOrange + 7, kGreen + 2, kAzure + 2, kBlue + 2, kMagenta + 1, kBlack}; bool first = true; for (int i = 2; i <= hFlagsVsOcc->GetNbinsX(); i++) { TH1D* hPass = hFlagsVsOcc->ProjectionY(Form("hFlagPass%d", i), i, i); - TH1D* hFrac = makeRatio(hPass, hAll, Form("hFracVsOccupancy_%s", hFlagsVsOcc->GetXaxis()->GetBinLabel(i)), "fraction of BCs passing the flags vs occupancy;occupancy by ITS tracks;fraction of BCs"); + TH1* hFrac = makeRatio(hPass, hAll, Form("hFracVsOccupancy_%s", hFlagsVsOcc->GetXaxis()->GetBinLabel(i)), "fraction of BCs passing the flags vs occupancy;occupancy by ITS tracks;fraction of BCs"); hFrac->SetLineColor(colors[(i - 2) % 7]); hFrac->SetMarkerColor(colors[(i - 2) % 7]); hFrac->SetMarkerStyle(20); @@ -310,7 +315,7 @@ void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char c = newPage("cBcPosition", "BC position in the TF and in the orbit", 1200, 800, 1, 3); if (hBcInTF && hBcInTFDefined) { c->cd(1); - TH1D* hFracDefined = makeRatio(hBcInTFDefined, hBcInTF, "hFracDefined", "fraction of BCs with calculated occupancy;BC in TF;fraction"); + TH1* hFracDefined = makeRatio(hBcInTFDefined, hBcInTF, "hFracDefined", "fraction of BCs with calculated occupancy;BC in TF;fraction"); hFracDefined->SetMinimum(0.); hFracDefined->SetMaximum(1.1); hFracDefined->Draw("hist"); @@ -336,7 +341,7 @@ void plotBcOccupancyQa(const char* fileName = "AnalysisResults.root", const char } if (hBcOccsFlags && hBcOccsFlags->GetBinContent(1) > 0) { c->cd(2)->SetBottomMargin(0.3); - auto* hFlagsFracBcOccs = static_cast(hBcOccsFlags->Clone("BcOccs_hFlagsFrac")); + auto* hFlagsFracBcOccs = dynamic_cast(hBcOccsFlags->Clone("BcOccs_hFlagsFrac")); hFlagsFracBcOccs->Scale(1. / hBcOccsFlags->GetBinContent(1)); hFlagsFracBcOccs->SetTitle("BcOccs: fraction of BCs passing each flag;;fraction of BCs"); hFlagsFracBcOccs->GetXaxis()->LabelsOption("v"); From 4e0d7381e9b9b46892e6d315391e4d29ccbbe4c7 Mon Sep 17 00:00:00 2001 From: Igor Altsybeev Date: Wed, 11 Nov 2026 21:30:25 +0100 Subject: [PATCH 14/14] dummy commit to trigger Formatting checks Clarified output description in comments. --- DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C b/DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C index c5bd175b986..12edbe8ea88 100644 --- a/DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C +++ b/DPG/Tasks/AOTEvent/macros/plotBcOccupancyQa.C @@ -15,7 +15,7 @@ /// Usage: /// root -l -b -q 'plotBcOccupancyQa.C("AnalysisResults.root")' /// root -l -b -q 'plotBcOccupancyQa.C("AnalysisResults.root", "bcOccupancyQa.pdf", "bc-occupancy-qa-task", "bcOccupancyQa.root")' -/// Output: multi-page PDF, and a ROOT file with the canvases and the histograms (by default: name of the PDF with .root) +/// Output: multi-page PDF, and a ROOT file with the canvases and the histograms /// /// \author Igor Altsybeev