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
6366class 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,6 +1221,7 @@ class TPCTimeSeries : public Task
11791221 return ;
11801222 }
11811223
1224+ // Saturate bin indices — edge bins act as overflow (Phase 0.2 fix)
11821225 const int tglBin = std::clamp (static_cast <int >(mTglBins * std::abs (trackTmp.getTgl ()) / mMaxTgl ) + mPhiBins ,
11831226 mPhiBins , mPhiBins + mTglBins - 1 );
11841227 const int phiBin = std::clamp (static_cast <int >(mPhiBins * trackTmp.getPhi () / o2::constants::math::TwoPI),
@@ -1354,6 +1397,42 @@ 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 — flat arrays per layer
1406+ uint8_t trdPattern = 0 ;
1407+ uint8_t nTRDTracklets = 0 ;
1408+ std::vector<uint64_t > trdTrackletWords (6 , 0 ); // raw Tracklet64 words
1409+ std::vector<int16_t > trdQ0 (6 , -1 ); // charge slice 0
1410+ std::vector<int16_t > trdQ1 (6 , -1 ); // charge slice 1
1411+ std::vector<int16_t > trdQ2 (6 , -1 ); // charge slice 2
1412+ std::vector<float > trdCalibX (6 , 0 .f ); // calibrated x
1413+ std::vector<float > trdCalibY (6 , 0 .f ); // calibrated y
1414+ std::vector<float > trdCalibZ (6 , 0 .f ); // calibrated z
1415+ auto itTRD = tpcToTRDMap.find (iTrk);
1416+ if (itTRD != tpcToTRDMap.end ()) {
1417+ const auto & trdData = itTRD->second ;
1418+ trdPattern = trdData.trdPattern ;
1419+ nTRDTracklets = trdData.nTRDTracklets ;
1420+ for (int iLay = 0 ; iLay < 6 ; ++iLay) {
1421+ if (trdData.trackletIndices [iLay] >= 0 ) {
1422+ const auto & trklt = trdTracklets[trdData.trackletIndices [iLay]];
1423+ trdTrackletWords[iLay] = trklt.getTrackletWord ();
1424+ trdQ0[iLay] = trklt.getQ0 ();
1425+ trdQ1[iLay] = trklt.getQ1 ();
1426+ trdQ2[iLay] = trklt.getQ2 ();
1427+ if (trdData.trackletIndices [iLay] < static_cast <int >(trdCalibTracklets.size ())) {
1428+ const auto & ctrklt = trdCalibTracklets[trdData.trackletIndices [iLay]];
1429+ trdCalibX[iLay] = ctrklt.getX ();
1430+ trdCalibY[iLay] = ctrklt.getY ();
1431+ trdCalibZ[iLay] = ctrklt.getZ ();
1432+ }
1433+ }
1434+ }
1435+ }
13571436 int typeSide = 2 ; // A- and C-Side cluster
13581437 if (trackFull.hasASideClustersOnly ()) {
13591438 typeSide = 0 ;
@@ -1488,6 +1567,19 @@ class TPCTimeSeries : public Task
14881567 << " mX_ITS=" << mx_ITS
14891568 << " nClITS=" << nClITS
14901569 << " chi2ITS=" << chi2ITS
1570+ << " itsClusterSizes=" << itsClusterSizes
1571+ << " itsHasSharedClusters=" << itsHasSharedClusters
1572+ << " itsPattern=" << itsPattern
1573+ // D2: TRD tracklet data
1574+ << " trdPattern=" << trdPattern
1575+ << " nTRDTracklets=" << nTRDTracklets
1576+ << " trdTrackletWords=" << trdTrackletWords
1577+ << " trdQ0=" << trdQ0
1578+ << " trdQ1=" << trdQ1
1579+ << " trdQ2=" << trdQ2
1580+ << " trdCalibX=" << trdCalibX
1581+ << " trdCalibY=" << trdCalibY
1582+ << " trdCalibZ=" << trdCalibZ
14911583 << " chi2match_ITSTPC=" << chi2match_ITSTPC
14921584 << " PID=" << trkOrig.getPID ().getID ()
14931585 // TPC cov at vertex (without vertex constrained)
@@ -1680,6 +1772,7 @@ class TPCTimeSeries : public Task
16801772
16811773 std::unordered_map<int , int > nContributors_ITS; // ITS: vertex ID -> n contributors
16821774 std::unordered_map<int , int > nContributors_ITSTPC; // ITS-TPC (and ITS-TPC-TRD, ITS-TPC-TOF, ITS-TPC-TRD-TOF): vertex ID -> n contributors
1775+ std::unordered_map<int , int > nContributors_TRD; // ITS-TPC-TRD (and ITS-TPC-TRD-TOF): vertex ID -> n TRD-matched PV contributors
16831776
16841777 // loop over collisions
16851778 if (!vertices.empty ()) {
@@ -1700,6 +1793,10 @@ class TPCTimeSeries : public Task
17001793 if (refITSTPC.isIndexSet ()) {
17011794 indicesITSTPC_vtx[refITSTPC] = vID;
17021795 ++nContributors_ITSTPC[vID];
1796+ // count TRD-matched PV contributors
1797+ if (source == TrkSrc::ITSTPCTRD || source == TrkSrc::ITSTPCTRDTOF ) {
1798+ ++nContributors_TRD[vID];
1799+ }
17031800 } else {
17041801 ++nContributors_ITS[vID];
17051802 }
@@ -1761,6 +1858,17 @@ class TPCTimeSeries : public Task
17611858 mBufferDCA .vertexY_ITSTPC_RMS .front () = avgVtxITSTPC[1 ].getStdDev ();
17621859 mBufferDCA .vertexZ_ITSTPC_RMS .front () = avgVtxITSTPC[2 ].getStdDev ();
17631860
1861+ // TRD matching fraction (summed over all vertices in this TF)
1862+ int sumITSTPCBased = 0 ;
1863+ int sumWithTRD = 0 ;
1864+ for (int ivtx = 0 ; ivtx < vertices.size (); ++ivtx) {
1865+ sumITSTPCBased += nContributors_ITSTPC[ivtx];
1866+ sumWithTRD += nContributors_TRD[ivtx];
1867+ }
1868+ mBufferDCA .nITSTPCBasedPVContributors .front () = sumITSTPCBased;
1869+ mBufferDCA .nITSTPCWithTRDPVContributors .front () = sumWithTRD;
1870+ mBufferDCA .fracTRD .front () = (sumITSTPCBased > 0 ) ? static_cast <float >(sumWithTRD) / sumITSTPCBased : std::nanf (" " );
1871+
17641872 // quantiles and truncated mean
17651873 RobustAverage avg (vertices.size (), false );
17661874 for (const auto & vtx : vertices) {
@@ -1850,6 +1958,10 @@ o2::framework::DataProcessorSpec getTPCTimeSeriesSpec(const bool disableWriter,
18501958 if (src[GTrackID::TPC ]) {
18511959 dataRequest->requestClusters (GTrackID::getSourcesMask (" TPC" ), useMC);
18521960 }
1961+ // D2: request TRD tracklets for tracks with TRD contribution
1962+ if (srcTracks[GTrackID::ITSTPCTRD ] || srcTracks[GTrackID::ITSTPCTRDTOF ]) {
1963+ dataRequest->requestTRDTracklets (useMC);
1964+ }
18531965
18541966 bool tpcOnly = srcTracks == GTrackID::getSourcesMask (" TPC" );
18551967 if (srcTracks.any () && !tpcOnly) {
0 commit comments