Skip to content

Commit 907c011

Browse files
miranov25miranov25
andauthored
TPC TimeSeries: fix silent track loss from binning overflow (#15658)
* TPC TimeSeries: fix silent track loss from binning overflow Bin indices for tgl, phi, qPt, and multiplicity were used as implicit track selection cuts: tracks outside histogram range were silently dropped (return). Replace with std::clamp — edge bins become overflow bins (standard ROOT convention). No change for tracks within range. Bug: changing --max-qPt or --mult-max removed tracks from ALL outputs (DCA, dEdx, etc.), not just the binned histograms. * TPC TimeSeries: clamp fix + ITS cluster sizes + TRD tracklets + TRD matching Phase 0.2 — binning overflow fix: - Replace bounds-check-and-return with std::clamp on all 4 bin indices - Edge bins act as saturated overflow; no tracks silently dropped Phase 0.3 D1 — ITS cluster sizes (per-track, unbinned): - itsClusterSizes: packed 4-bit per layer (bit 28 kSharedClusters masked) - itsHasSharedClusters, itsPattern: 7-bit layer hit pattern Phase 0.3 D2 — TRD tracklet objects (per-track, unbinned): - Native Tracklet64[6] and CalibratedTracklet[6] per layer - trdPattern (6-bit validity mask), nTRDTracklets - requestTRDTracklets added to DataRequest Phase 0.3 D3 — TRD matching fraction (per-TF): - nITSTPCBasedPVContributors, nITSTPCWithTRDPVContributors, fracTRD - NaN for zero denominator. ClassDefNV 7 -> 8. * TPC TimeSeries: store TRD tracklets as native objects (std::vector) Replace flat primitive arrays with std::vector<Tracklet64> and std::vector<CalibratedTracklet>. std::array failed ROOT serialization (missing ShowMember); std::vector with ROOT dictionary works. * Clang --------- Co-authored-by: miranov25 <marian.ivanov@cern.cg>
1 parent 5240041 commit 907c011

2 files changed

Lines changed: 114 additions & 12 deletions

File tree

Detectors/Calibration/include/DetectorsCalibration/IntegratedClusterCalibrator.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,10 @@ struct TimeSeriesITSTPC {
365365
std::vector<float> vertexY_ITSTPC_RMS; ///< vertex y RMS with ITS-TPC cut (nContributorsITS + nContributorsITSTPC)<0.95
366366
std::vector<float> vertexZ_ITSTPC_RMS; ///< vertex z RMS with ITS-TPC cut (nContributorsITS + nContributorsITSTPC)<0.95
367367

368+
std::vector<float> nITSTPCBasedPVContributors; ///< number of ITS-TPC-based PV contributors (denominator for TRD matching fraction)
369+
std::vector<float> nITSTPCWithTRDPVContributors; ///< number of ITS-TPC-TRD PV contributors (numerator for TRD matching fraction)
370+
std::vector<float> fracTRD; ///< fraction of ITS-TPC PV contributors with TRD match (NaN if denominator=0)
371+
368372
int quantileValues = 23; ///<! number of values in quantiles + truncated mean (hardcoded for the moment)
369373
std::vector<float> nVertexContributors_Quantiles; ///< number of primary vertices for quantiles 0.1, 0.2, ... 0.9 and truncated mean values 0.05->0.95, 0.1->0.9, 0.2->0.8
370374

@@ -498,12 +502,15 @@ struct TimeSeriesITSTPC {
498502
vertexX_ITSTPC_RMS.resize(nTotalVtx);
499503
vertexY_ITSTPC_RMS.resize(nTotalVtx);
500504
vertexZ_ITSTPC_RMS.resize(nTotalVtx);
505+
nITSTPCBasedPVContributors.resize(nTotalVtx);
506+
nITSTPCWithTRDPVContributors.resize(nTotalVtx);
507+
fracTRD.resize(nTotalVtx);
501508

502509
const int nTotalQ = quantileValues * nTotal / mTSTPC.getNBins();
503510
nVertexContributors_Quantiles.resize(nTotalQ);
504511
}
505512

506-
ClassDefNV(TimeSeriesITSTPC, 7);
513+
ClassDefNV(TimeSeriesITSTPC, 8);
507514
};
508515

509516
} // end namespace tpc

Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx

Lines changed: 106 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
4141
#include <chrono>
4242
#include "DataFormatsTPC/PIDResponse.h"
4343
#include "DataFormatsITS/TrackITS.h"
44+
#include "DataFormatsTRD/TrackTRD.h"
45+
#include "DataFormatsTRD/Tracklet64.h"
46+
#include "DataFormatsTRD/CalibratedTracklet.h"
4447
#include "TROOT.h"
4548
#include "ReconstructionDataFormats/MatchInfoTOF.h"
4649
#include "DataFormatsTOF/Cluster.h"
@@ -63,6 +66,13 @@ namespace tpc
6366
class TPCTimeSeries : public Task
6467
{
6568
public:
69+
/// D2: per-track TRD tracklet lookup data
70+
struct TRDTrackletData {
71+
uint8_t trdPattern = 0;
72+
uint8_t nTRDTracklets = 0;
73+
int trackletIndices[6] = {-1, -1, -1, -1, -1, -1};
74+
};
75+
6676
/// \constructor
6777
TPCTimeSeries(std::shared_ptr<o2::base::GRPGeomRequest> req, const bool disableWriter, const o2::base::Propagator::MatCorrType matType, const bool enableUnbinnedWriter, const bool tpcOnly, std::shared_ptr<o2::globaltracking::DataRequest> dr) : mCCDBRequest(req), mDisableWriter(disableWriter), mMatType(matType), mUnbinnedWriter(enableUnbinnedWriter), mTPCOnly(tpcOnly), mDataRequest(dr) {};
6878

@@ -303,6 +313,38 @@ class TPCTimeSeries : public Task
303313
// find nearest vertex of tracks which have no vertex assigned
304314
findNearesVertex(tracksTPC, vertices);
305315

316+
// D2: build TPC track index → TRD tracklet data map (for unbinned output)
317+
// For each TPC track that has a TRD match, store the TrackTRD tracklet indices
318+
std::unordered_map<unsigned int, TRDTrackletData> tpcToTRDMap;
319+
auto trdTracklets = mTPCOnly ? gsl::span<const o2::trd::Tracklet64>() : recoData.getTRDTracklets();
320+
auto trdCalibTracklets = mTPCOnly ? gsl::span<const o2::trd::CalibratedTracklet>() : recoData.getTRDCalibratedTracklets();
321+
if (mUnbinnedWriter && !mTPCOnly) {
322+
// scan ITS-TPC-TRD tracks
323+
auto itstpctrdTracks = recoData.getITSTPCTRDTracks<o2::trd::TrackTRD>();
324+
for (unsigned int ig = 0; ig < itstpctrdTracks.size(); ++ig) {
325+
auto gid = GTrackID(ig, GTrackID::ITSTPCTRD);
326+
auto refTPC = recoData.getTPCContributorGID(gid);
327+
if (!refTPC.isIndexSet()) {
328+
continue;
329+
}
330+
auto refTRD = recoData.getSingleDetectorRefs(gid)[GTrackID::TRD];
331+
if (!refTRD.isIndexSet()) {
332+
continue;
333+
}
334+
const auto& trdTrack = recoData.getTrack<o2::trd::TrackTRD>(refTRD);
335+
TRDTrackletData trdData;
336+
for (int iLay = 0; iLay < 6; ++iLay) {
337+
auto trkltId = trdTrack.getTrackletIndex(iLay);
338+
if (trkltId >= 0) {
339+
trdData.trdPattern |= (1 << iLay);
340+
trdData.nTRDTracklets++;
341+
trdData.trackletIndices[iLay] = trkltId;
342+
}
343+
}
344+
tpcToTRDMap[refTPC] = trdData;
345+
}
346+
}
347+
306348
// getting cluster references for cluster bitmask
307349
if (mUnbinnedWriter) {
308350
mTPCTrackClIdx = pc.inputs().get<gsl::span<o2::tpc::TPCClRefElem>>("trackTPCClRefs");
@@ -472,7 +514,7 @@ class TPCTimeSeries : public Task
472514
auto myThread = [&](int iThread) {
473515
for (size_t i = iThread; i < loopEnd; i += mNThreads) {
474516
if (acceptTrack(tracksTPC[i])) {
475-
fillDCA(tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters);
517+
fillDCA(tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters, tpcToTRDMap, trdTracklets, trdCalibTracklets);
476518
}
477519
}
478520
};
@@ -489,7 +531,7 @@ class TPCTimeSeries : public Task
489531
auto myThread = [&](int iThread) {
490532
for (size_t i = iThread; i < loopEnd; i += mNThreads) {
491533
if (acceptTrack(tracksTPC[i])) {
492-
fillDCA(tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters);
534+
fillDCA(tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters, tpcToTRDMap, trdTracklets, trdCalibTracklets);
493535
}
494536
}
495537
};
@@ -1133,7 +1175,7 @@ class TPCTimeSeries : public Task
11331175
return isGoodTrack;
11341176
}
11351177

1136-
void fillDCA(const gsl::span<const TrackTPC> tracksTPC, const gsl::span<const o2::dataformats::TrackTPCITS> tracksITSTPC, const gsl::span<const o2::dataformats::PrimaryVertex> vertices, const int iTrk, const int iThread, const std::unordered_map<unsigned int, std::array<int, 2>>& indicesITSTPC, const gsl::span<const o2::its::TrackITS> tracksITS, const std::vector<std::tuple<int, float, float, o2::track::TrackLTIntegral, double, float, unsigned int, unsigned short>>& idxTPCTrackToTOFCluster, const gsl::span<const o2::tof::Cluster> tofClusters)
1178+
void fillDCA(const gsl::span<const TrackTPC> tracksTPC, const gsl::span<const o2::dataformats::TrackTPCITS> tracksITSTPC, const gsl::span<const o2::dataformats::PrimaryVertex> vertices, const int iTrk, const int iThread, const std::unordered_map<unsigned int, std::array<int, 2>>& indicesITSTPC, const gsl::span<const o2::its::TrackITS> tracksITS, const std::vector<std::tuple<int, float, float, o2::track::TrackLTIntegral, double, float, unsigned int, unsigned short>>& idxTPCTrackToTOFCluster, const gsl::span<const o2::tof::Cluster> tofClusters, const std::unordered_map<unsigned int, TRDTrackletData>& tpcToTRDMap, const gsl::span<const o2::trd::Tracklet64> trdTracklets, const gsl::span<const o2::trd::CalibratedTracklet> trdCalibTracklets)
11371179
{
11381180
const auto& trackFull = tracksTPC[iTrk];
11391181
const bool isGoodTrack = checkTrack(trackFull);
@@ -1179,21 +1221,22 @@ class TPCTimeSeries : public Task
11791221
return;
11801222
}
11811223

1182-
const int tglBin = mTglBins * std::abs(trackTmp.getTgl()) / mMaxTgl + mPhiBins;
1183-
const int phiBin = mPhiBins * trackTmp.getPhi() / o2::constants::math::TwoPI;
1224+
// Saturate bin indices — edge bins act as overflow (Phase 0.2 fix)
1225+
const int tglBin = std::clamp(static_cast<int>(mTglBins * std::abs(trackTmp.getTgl()) / mMaxTgl) + mPhiBins,
1226+
mPhiBins, mPhiBins + mTglBins - 1);
1227+
const int phiBin = std::clamp(static_cast<int>(mPhiBins * trackTmp.getPhi() / o2::constants::math::TwoPI),
1228+
0, mPhiBins - 1);
11841229

11851230
const int offsQPtBin = mPhiBins + mTglBins;
1186-
const int qPtBin = offsQPtBin + mQPtBins * (trackTmp.getQ2Pt() + mMaxQPt) / (2 * mMaxQPt);
1231+
const int qPtBin = std::clamp(offsQPtBin + static_cast<int>(mQPtBins * (trackTmp.getQ2Pt() + mMaxQPt) / (2 * mMaxQPt)),
1232+
offsQPtBin, offsQPtBin + mQPtBins - 1);
11871233
const int localMult = mNTracksWindow[iTrk];
11881234

11891235
const int offsMult = offsQPtBin + mQPtBins;
1190-
const int multBin = offsMult + mMultBins * localMult / mMultMax;
1236+
const int multBin = std::clamp(offsMult + static_cast<int>(mMultBins * localMult / mMultMax),
1237+
offsMult, offsMult + mMultBins - 1);
11911238
const int nBins = getNBins();
11921239

1193-
if ((phiBin < 0) || (phiBin > mPhiBins) || (tglBin < mPhiBins) || (tglBin > offsQPtBin) || (qPtBin < offsQPtBin) || (qPtBin > offsMult) || (multBin < offsMult) || (multBin > offsMult + mMultBins)) {
1194-
return;
1195-
}
1196-
11971240
float sigmaY2 = 0;
11981241
float sigmaZ2 = 0;
11991242
const int sector = o2::math_utils::angle2Sector(trackTmp.getPhiPos());
@@ -1354,6 +1397,30 @@ class TPCTimeSeries : public Task
13541397
const float chi2match_ITSTPC = hasITSTPC ? tracksITSTPC[idxITSTPC.front()].getChi2Match() : -1;
13551398
const int nClITS = idxITSCheck ? tracksITS[idxITSTrack].getNClusters() : -1;
13561399
const int chi2ITS = idxITSCheck ? tracksITS[idxITSTrack].getChi2() : -1;
1400+
// D1: ITS cluster sizes (4-bit per layer, mask bit 28 = kSharedClusters)
1401+
const uint32_t itsClusterSizes = idxITSCheck ? (static_cast<uint32_t>(tracksITS[idxITSTrack].getClusterSizes()) & 0x0FFFFFFFu) : 0u;
1402+
const bool itsHasSharedClusters = idxITSCheck ? tracksITS[idxITSTrack].hasSharedClusters() : false;
1403+
const uint32_t itsPattern = idxITSCheck ? (tracksITS[idxITSTrack].getPattern() & 0x7Fu) : 0u;
1404+
1405+
// D2: TRD tracklet data — native objects per layer
1406+
uint8_t trdPattern = 0;
1407+
uint8_t nTRDTracklets = 0;
1408+
std::vector<o2::trd::Tracklet64> trdTrackletVec(6);
1409+
std::vector<o2::trd::CalibratedTracklet> trdCalibVec(6);
1410+
auto itTRD = tpcToTRDMap.find(iTrk);
1411+
if (itTRD != tpcToTRDMap.end()) {
1412+
const auto& trdData = itTRD->second;
1413+
trdPattern = trdData.trdPattern;
1414+
nTRDTracklets = trdData.nTRDTracklets;
1415+
for (int iLay = 0; iLay < 6; ++iLay) {
1416+
if (trdData.trackletIndices[iLay] >= 0) {
1417+
trdTrackletVec[iLay] = trdTracklets[trdData.trackletIndices[iLay]];
1418+
if (trdData.trackletIndices[iLay] < static_cast<int>(trdCalibTracklets.size())) {
1419+
trdCalibVec[iLay] = trdCalibTracklets[trdData.trackletIndices[iLay]];
1420+
}
1421+
}
1422+
}
1423+
}
13571424
int typeSide = 2; // A- and C-Side cluster
13581425
if (trackFull.hasASideClustersOnly()) {
13591426
typeSide = 0;
@@ -1488,6 +1555,14 @@ class TPCTimeSeries : public Task
14881555
<< "mX_ITS=" << mx_ITS
14891556
<< "nClITS=" << nClITS
14901557
<< "chi2ITS=" << chi2ITS
1558+
<< "itsClusterSizes=" << itsClusterSizes
1559+
<< "itsHasSharedClusters=" << itsHasSharedClusters
1560+
<< "itsPattern=" << itsPattern
1561+
// D2: TRD tracklet data
1562+
<< "trdPattern=" << trdPattern
1563+
<< "nTRDTracklets=" << nTRDTracklets
1564+
<< "trdTracklets=" << trdTrackletVec
1565+
<< "trdCalibTracklets=" << trdCalibVec
14911566
<< "chi2match_ITSTPC=" << chi2match_ITSTPC
14921567
<< "PID=" << trkOrig.getPID().getID()
14931568
// TPC cov at vertex (without vertex constrained)
@@ -1680,6 +1755,7 @@ class TPCTimeSeries : public Task
16801755

16811756
std::unordered_map<int, int> nContributors_ITS; // ITS: vertex ID -> n contributors
16821757
std::unordered_map<int, int> nContributors_ITSTPC; // ITS-TPC (and ITS-TPC-TRD, ITS-TPC-TOF, ITS-TPC-TRD-TOF): vertex ID -> n contributors
1758+
std::unordered_map<int, int> nContributors_TRD; // ITS-TPC-TRD (and ITS-TPC-TRD-TOF): vertex ID -> n TRD-matched PV contributors
16831759

16841760
// loop over collisions
16851761
if (!vertices.empty()) {
@@ -1700,6 +1776,10 @@ class TPCTimeSeries : public Task
17001776
if (refITSTPC.isIndexSet()) {
17011777
indicesITSTPC_vtx[refITSTPC] = vID;
17021778
++nContributors_ITSTPC[vID];
1779+
// count TRD-matched PV contributors
1780+
if (source == TrkSrc::ITSTPCTRD || source == TrkSrc::ITSTPCTRDTOF) {
1781+
++nContributors_TRD[vID];
1782+
}
17031783
} else {
17041784
++nContributors_ITS[vID];
17051785
}
@@ -1761,6 +1841,17 @@ class TPCTimeSeries : public Task
17611841
mBufferDCA.vertexY_ITSTPC_RMS.front() = avgVtxITSTPC[1].getStdDev();
17621842
mBufferDCA.vertexZ_ITSTPC_RMS.front() = avgVtxITSTPC[2].getStdDev();
17631843

1844+
// TRD matching fraction (summed over all vertices in this TF)
1845+
int sumITSTPCBased = 0;
1846+
int sumWithTRD = 0;
1847+
for (int ivtx = 0; ivtx < vertices.size(); ++ivtx) {
1848+
sumITSTPCBased += nContributors_ITSTPC[ivtx];
1849+
sumWithTRD += nContributors_TRD[ivtx];
1850+
}
1851+
mBufferDCA.nITSTPCBasedPVContributors.front() = sumITSTPCBased;
1852+
mBufferDCA.nITSTPCWithTRDPVContributors.front() = sumWithTRD;
1853+
mBufferDCA.fracTRD.front() = (sumITSTPCBased > 0) ? static_cast<float>(sumWithTRD) / sumITSTPCBased : std::nanf("");
1854+
17641855
// quantiles and truncated mean
17651856
RobustAverage avg(vertices.size(), false);
17661857
for (const auto& vtx : vertices) {
@@ -1850,6 +1941,10 @@ o2::framework::DataProcessorSpec getTPCTimeSeriesSpec(const bool disableWriter,
18501941
if (src[GTrackID::TPC]) {
18511942
dataRequest->requestClusters(GTrackID::getSourcesMask("TPC"), useMC);
18521943
}
1944+
// D2: request TRD tracklets for tracks with TRD contribution
1945+
if (srcTracks[GTrackID::ITSTPCTRD] || srcTracks[GTrackID::ITSTPCTRDTOF]) {
1946+
dataRequest->requestTRDTracklets(useMC);
1947+
}
18531948

18541949
bool tpcOnly = srcTracks == GTrackID::getSourcesMask("TPC");
18551950
if (srcTracks.any() && !tpcOnly) {

0 commit comments

Comments
 (0)