From 8b9925a47581caf081ad0969592d4baa2dd2e698 Mon Sep 17 00:00:00 2001 From: miranov Date: Wed, 5 Aug 2026 15:40:15 +0200 Subject: [PATCH 1/9] =?UTF-8?q?TPC=20TimeSeries:=20Phase=200.4=20=E2=80=94?= =?UTF-8?q?=20TrackParCov=20at=20R=3D275=20+=20vertex=20+=20chi2Match?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Store full TrackParCov (params + covariance) at three reference surfaces: tpcOut: TPC outer at kTPCTRDMiddle (275 cm), from getParamOut() trdIn: TRD matched at 275 cm, same alpha as TPC out tpcAtVtx: TPC track at vertex (propagated to DCA) itstpcAtVtx: ITS-TPC track at vertex (propagated to DCA) chi2Match_Phi: reusable 3x3 chi2 (Y, Snp, Q2Pt) — 3 DOF chi2Match_Z: reusable 2x2 chi2 (Z, Tgl) — 2 DOF Both stored per track: chi2PhiTPCTRD, chi2ZTPCTRD. --- .../TPC/workflow/src/TPCTimeSeriesSpec.cxx | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx index d9dc774de812e..c4b1ac1a673c8 100644 --- a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx +++ b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx @@ -71,8 +71,61 @@ class TPCTimeSeries : public Task uint8_t trdPattern = 0; uint8_t nTRDTracklets = 0; int trackletIndices[6] = {-1, -1, -1, -1, -1, -1}; + o2::track::TrackParCov trdTrackParCov; // Phase 0.4: TRD track for propagation to R=275 + bool hasTRDTrackPar = false; }; + /// Chi2 match in bending plane (Y, Snp, Q2Pt) — 3 DOF + /// Both tracks must be at the same X and alpha. + /// Returns -1 if covariance matrix is singular. + static float chi2Match_Phi(const o2::track::TrackParCov& trk1, const o2::track::TrackParCov& trk2) + { + float d0 = trk1.getY() - trk2.getY(); + float d1 = trk1.getSnp() - trk2.getSnp(); + float d2 = trk1.getQ2Pt() - trk2.getQ2Pt(); + // Combined covariance C = C1 + C2 + float c00 = trk1.getSigmaY2() + trk2.getSigmaY2(); + float c01 = trk1.getSigmaSnpY() + trk2.getSigmaSnpY(); + float c02 = trk1.getSigma1PtY() + trk2.getSigma1PtY(); + float c11 = trk1.getSigmaSnp2() + trk2.getSigmaSnp2(); + float c12 = trk1.getSigma1PtSnp() + trk2.getSigma1PtSnp(); + float c22 = trk1.getSigma1Pt2() + trk2.getSigma1Pt2(); + float det = c00 * (c11 * c22 - c12 * c12) + - c01 * (c01 * c22 - c12 * c02) + + c02 * (c01 * c12 - c11 * c02); + if (std::abs(det) < 1e-30f) { + return -1.f; + } + float detInv = 1.f / det; + float i00 = (c11 * c22 - c12 * c12) * detInv; + float i01 = (c02 * c12 - c01 * c22) * detInv; + float i02 = (c01 * c12 - c02 * c11) * detInv; + float i11 = (c00 * c22 - c02 * c02) * detInv; + float i12 = (c01 * c02 - c00 * c12) * detInv; + float i22 = (c00 * c11 - c01 * c01) * detInv; + return d0 * (i00 * d0 + i01 * d1 + i02 * d2) + + d1 * (i01 * d0 + i11 * d1 + i12 * d2) + + d2 * (i02 * d0 + i12 * d1 + i22 * d2); + } + + /// Chi2 match in non-bending plane (Z, Tgl) — 2 DOF + /// Both tracks must be at the same X and alpha. + /// Returns -1 if covariance matrix is singular. + static float chi2Match_Z(const o2::track::TrackParCov& trk1, const o2::track::TrackParCov& trk2) + { + float d0 = trk1.getZ() - trk2.getZ(); + float d1 = trk1.getTgl() - trk2.getTgl(); + float c00 = trk1.getSigmaZ2() + trk2.getSigmaZ2(); + float c01 = trk1.getSigmaTglZ() + trk2.getSigmaTglZ(); + float c11 = trk1.getSigmaTgl2() + trk2.getSigmaTgl2(); + float det = c00 * c11 - c01 * c01; + if (std::abs(det) < 1e-30f) { + return -1.f; + } + float detInv = 1.f / det; + return (d0 * d0 * c11 - 2.f * d0 * d1 * c01 + d1 * d1 * c00) * detInv; + } + /// \constructor TPCTimeSeries(std::shared_ptr req, const bool disableWriter, const o2::base::Propagator::MatCorrType matType, const bool enableUnbinnedWriter, const bool tpcOnly, std::shared_ptr dr) : mCCDBRequest(req), mDisableWriter(disableWriter), mMatType(matType), mUnbinnedWriter(enableUnbinnedWriter), mTPCOnly(tpcOnly), mDataRequest(dr) {}; @@ -341,6 +394,8 @@ class TPCTimeSeries : public Task trdData.trackletIndices[iLay] = trkltId; } } + trdData.trdTrackParCov = o2::track::TrackParCov(trdTrack); + trdData.hasTRDTrackPar = true; tpcToTRDMap[refTPC] = trdData; } } @@ -1421,6 +1476,55 @@ class TPCTimeSeries : public Task } } } + + // Phase 0.4: TPC out / TRD in at kTPCTRDMiddle, same frame, same X + static constexpr float kTPCTRDMiddle = 275.f; // cm, midpoint TPC outer / TRD layer 0 + auto propagator = o2::base::Propagator::Instance(); + + // D1: TPC outer → kTPCTRDMiddle + o2::track::TrackParCov tpcOutCov(tracksTPC[iTrk].getParamOut()); + uint8_t tpcOutOK = 2; // default: propagation failed + { + // rotate to sector angle at current position + float sn = std::sin(tpcOutCov.getAlpha()); + float cs = std::cos(tpcOutCov.getAlpha()); + float gy = tpcOutCov.getX() * sn + tpcOutCov.getY() * cs; + float gx = tpcOutCov.getX() * cs - tpcOutCov.getY() * sn; + if (tpcOutCov.rotate(std::atan2(gy, gx)) && + propagator->propagateTo(tpcOutCov, kTPCTRDMiddle, false, mMaxSnp, mCoarseStep, mMatType)) { + tpcOutOK = 0; + } + } + + // D2: TRD matched → kTPCTRDMiddle, rotated to SAME alpha as TPC out + o2::track::TrackParCov trdInCov; + uint8_t trdInOK = 1; // default: no TRD match + float chi2PhiTPCTRD = -1.f; + float chi2ZTPCTRD = -1.f; + if (itTRD != tpcToTRDMap.end() && itTRD->second.hasTRDTrackPar && tpcOutOK == 0) { + trdInCov = o2::track::TrackParCov(itTRD->second.trdTrackParCov); + // rotate to TPC alpha FIRST, then propagate — guarantees same frame + same X + if (trdInCov.rotate(tpcOutCov.getAlpha()) && + propagator->propagateTo(trdInCov, kTPCTRDMiddle, false, mMaxSnp, mCoarseStep, mMatType)) { + trdInOK = 0; + chi2PhiTPCTRD = chi2Match_Phi(tpcOutCov, trdInCov); + chi2ZTPCTRD = chi2Match_Z(tpcOutCov, trdInCov); + } else { + trdInOK = 2; // propagation failed + } + } + + // ITS-TPC track at vertex (full TrackParCov, propagated to DCA) + o2::track::TrackParCov itstpcAtVtx; + bool hasITSTPCAtVtx = false; + if (hasITSTPC) { + itstpcAtVtx = tracksITSTPC[idxITSTPC.front()]; + o2::dataformats::DCA dcaTmp; + if (propagator->propagateToDCA(vertex.getXYZ(), itstpcAtVtx, propagator->getNominalBz(), mFineStep, mMatType, &dcaTmp)) { + hasITSTPCAtVtx = true; + } + } + int typeSide = 2; // A- and C-Side cluster if (trackFull.hasASideClustersOnly()) { typeSide = 0; @@ -1563,6 +1667,17 @@ class TPCTimeSeries : public Task << "nTRDTracklets=" << nTRDTracklets << "trdTracklets=" << trdTrackletVec << "trdCalibTracklets=" << trdCalibVec + // Phase 0.4: TPC out / TRD in at R=275 cm + << "tpcOut=" << tpcOutCov + << "tpcOutOK=" << tpcOutOK + << "trdIn=" << trdInCov + << "trdInOK=" << trdInOK + << "chi2PhiTPCTRD=" << chi2PhiTPCTRD + << "chi2ZTPCTRD=" << chi2ZTPCTRD + // Track parameters at vertex (full TrackParCov) + << "tpcAtVtx=" << track + << "itstpcAtVtx=" << itstpcAtVtx + << "hasITSTPCAtVtx=" << hasITSTPCAtVtx << "chi2match_ITSTPC=" << chi2match_ITSTPC << "PID=" << trkOrig.getPID().getID() // TPC cov at vertex (without vertex constrained) From 310a851f7e7d4f3ff05f1d555b3bb8cd7dd9100d Mon Sep 17 00:00:00 2001 From: miranov Date: Wed, 5 Aug 2026 16:24:26 +0200 Subject: [PATCH 2/9] Phase_0_4 remove unnecssary propoagation --- Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx index c4b1ac1a673c8..f11fe3180e3f1 100644 --- a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx +++ b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx @@ -1514,17 +1514,6 @@ class TPCTimeSeries : public Task } } - // ITS-TPC track at vertex (full TrackParCov, propagated to DCA) - o2::track::TrackParCov itstpcAtVtx; - bool hasITSTPCAtVtx = false; - if (hasITSTPC) { - itstpcAtVtx = tracksITSTPC[idxITSTPC.front()]; - o2::dataformats::DCA dcaTmp; - if (propagator->propagateToDCA(vertex.getXYZ(), itstpcAtVtx, propagator->getNominalBz(), mFineStep, mMatType, &dcaTmp)) { - hasITSTPCAtVtx = true; - } - } - int typeSide = 2; // A- and C-Side cluster if (trackFull.hasASideClustersOnly()) { typeSide = 0; @@ -1676,8 +1665,6 @@ class TPCTimeSeries : public Task << "chi2ZTPCTRD=" << chi2ZTPCTRD // Track parameters at vertex (full TrackParCov) << "tpcAtVtx=" << track - << "itstpcAtVtx=" << itstpcAtVtx - << "hasITSTPCAtVtx=" << hasITSTPCAtVtx << "chi2match_ITSTPC=" << chi2match_ITSTPC << "PID=" << trkOrig.getPID().getID() // TPC cov at vertex (without vertex constrained) From cc2942399bebd4a2192d841149d66698ae39931b Mon Sep 17 00:00:00 2001 From: miranov Date: Wed, 28 Oct 2026 07:49:56 +0100 Subject: [PATCH 3/9] TPC TimeSeries: optional TrackPar output (default off) and TRD readout flag - tpcOut, trdIn and tpcAtVtx are written only with --store-full-trackpar (default off), as o2::track::TrackPar without covariance. The TPC/TRD propagation to the common local X = 275 cm plane, the TPC-TRD matching chi2 and the per-track TrackParCov copy in the TRD map run only with this option; otherwise tpcOutOK/trdInOK = 255 and the chi2 values are -1. With the option on, the three objects are the first branches of the tree. - New per-entry flag trdInReadout (TRD in detector readout from GRPECS: 1 yes, 0 no, 255 unknown). GRPECS is requested only when the unbinned output is enabled. - Deferred, not fixed: propagation targets local X (not lab radius), and the TRD starting state is the TrackTRD base state. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01YYeB7SW1TM7vU8qhVHiG61 --- .../TPC/workflow/src/TPCTimeSeriesSpec.cxx | 131 +++++++++++------- 1 file changed, 84 insertions(+), 47 deletions(-) diff --git a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx index f11fe3180e3f1..62f09229db45e 100644 --- a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx +++ b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx @@ -71,7 +71,7 @@ class TPCTimeSeries : public Task uint8_t trdPattern = 0; uint8_t nTRDTracklets = 0; int trackletIndices[6] = {-1, -1, -1, -1, -1, -1}; - o2::track::TrackParCov trdTrackParCov; // Phase 0.4: TRD track for propagation to R=275 + o2::track::TrackParCov trdTrackParCov; // TRD track state for propagation to the common local X = 275 cm plane; filled only with --store-full-trackpar bool hasTRDTrackPar = false; }; @@ -90,9 +90,7 @@ class TPCTimeSeries : public Task float c11 = trk1.getSigmaSnp2() + trk2.getSigmaSnp2(); float c12 = trk1.getSigma1PtSnp() + trk2.getSigma1PtSnp(); float c22 = trk1.getSigma1Pt2() + trk2.getSigma1Pt2(); - float det = c00 * (c11 * c22 - c12 * c12) - - c01 * (c01 * c22 - c12 * c02) - + c02 * (c01 * c12 - c11 * c02); + float det = c00 * (c11 * c22 - c12 * c12) - c01 * (c01 * c22 - c12 * c02) + c02 * (c01 * c12 - c11 * c02); if (std::abs(det) < 1e-30f) { return -1.f; } @@ -103,9 +101,7 @@ class TPCTimeSeries : public Task float i11 = (c00 * c22 - c02 * c02) * detInv; float i12 = (c01 * c02 - c00 * c12) * detInv; float i22 = (c00 * c11 - c01 * c01) * detInv; - return d0 * (i00 * d0 + i01 * d1 + i02 * d2) - + d1 * (i01 * d0 + i11 * d1 + i12 * d2) - + d2 * (i02 * d0 + i12 * d1 + i22 * d2); + return d0 * (i00 * d0 + i01 * d1 + i02 * d2) + d1 * (i01 * d0 + i11 * d1 + i12 * d2) + d2 * (i02 * d0 + i12 * d1 + i22 * d2); } /// Chi2 match in non-bending plane (Z, Tgl) — 2 DOF @@ -165,6 +161,7 @@ class TPCTimeSeries : public Task mXOuterMatching = ic.options().get("refX-for-outer-ITS"); mUseMinBiasTrigger = !ic.options().get("disable-min-bias-trigger"); mMaxOccupancyHistBins = ic.options().get("max-occupancy-bins"); + mStoreFullTrackPar = ic.options().get("store-full-trackpar"); if (mUnbinnedWriter) { for (int iThread = 0; iThread < mNThreads; ++iThread) { @@ -191,6 +188,13 @@ class TPCTimeSeries : public Task void run(ProcessingContext& pc) final { o2::base::GRPGeomHelper::instance().checkUpdates(pc); + // run-level TRD readout flag from GRPECS (unbinned output only): 1 = TRD in readout, 0 = not in readout, + // 255 = unknown (defensive: GRPECS is requested only with the unbinned writer) + mTRDInReadout = 255; + if (mUnbinnedWriter) { + const auto* grpecs = o2::base::GRPGeomHelper::instance().getGRPECS(); + mTRDInReadout = grpecs ? static_cast(grpecs->isDetReadOut(DetID::TRD) ? 1 : 0) : static_cast(255); + } mTPCVDriftHelper.extractCCDBInputs(pc); mPTHelper.extractCCDBInputs(pc); if (mTPCVDriftHelper.isUpdated()) { @@ -394,8 +398,10 @@ class TPCTimeSeries : public Task trdData.trackletIndices[iLay] = trkltId; } } - trdData.trdTrackParCov = o2::track::TrackParCov(trdTrack); - trdData.hasTRDTrackPar = true; + if (mStoreFullTrackPar) { + trdData.trdTrackParCov = o2::track::TrackParCov(trdTrack); // TrackTRD base state, not validated as the outer/entrance state (open Phase 0.4 finding) + trdData.hasTRDTrackPar = true; + } tpcToTRDMap[refTPC] = trdData; } } @@ -1211,6 +1217,8 @@ class TPCTimeSeries : public Task float mMaxdEdxRegionRatio{0.5}; ///< maximum abs dedx region ratio: log(dedx_region/dedx) float mSamplingFactor{0.1}; ///< sampling factor in case sampling is used for unbinned data bool mSampleTsallis{false}; ///< perform sampling of unbinned data + bool mStoreFullTrackPar{false}; ///< experimental: compute and store tpcOut/trdIn/tpcAtVtx TrackPar and TPC-TRD chi2 (constant per job) + uint8_t mTRDInReadout{255}; ///< TRD in detector readout (GRPECS): 1 yes, 0 no, 255 unknown std::vector mGenerator; ///< random generator for debug tree sampling std::vector> mStreamer; ///< streamer for unbinned data float mXOuterMatching{60}; ///< reference x where delta track parameters for TPC with ITS-TPC track are stored in skimmed data @@ -1477,40 +1485,46 @@ class TPCTimeSeries : public Task } } - // Phase 0.4: TPC out / TRD in at kTPCTRDMiddle, same frame, same X - static constexpr float kTPCTRDMiddle = 275.f; // cm, midpoint TPC outer / TRD layer 0 - auto propagator = o2::base::Propagator::Instance(); - - // D1: TPC outer → kTPCTRDMiddle - o2::track::TrackParCov tpcOutCov(tracksTPC[iTrk].getParamOut()); - uint8_t tpcOutOK = 2; // default: propagation failed - { - // rotate to sector angle at current position - float sn = std::sin(tpcOutCov.getAlpha()); - float cs = std::cos(tpcOutCov.getAlpha()); - float gy = tpcOutCov.getX() * sn + tpcOutCov.getY() * cs; - float gx = tpcOutCov.getX() * cs - tpcOutCov.getY() * sn; - if (tpcOutCov.rotate(std::atan2(gy, gx)) && - propagator->propagateTo(tpcOutCov, kTPCTRDMiddle, false, mMaxSnp, mCoarseStep, mMatType)) { - tpcOutOK = 0; - } - } - - // D2: TRD matched → kTPCTRDMiddle, rotated to SAME alpha as TPC out + // Optional TPC-out / TRD-in states on a common local X plane (experimental, --store-full-trackpar, default off). + // Open Phase 0.4 findings, deferred: the target is local X = kTPCTRDMiddle in the frame rotated to the track + // position angle (lab radius = sqrt(X^2 + Y^2), not exactly 275 cm), and the TRD starting state is the TrackTRD + // base state, not validated as the outer/entrance state. With the switch off nothing below is computed. + static constexpr float kTPCTRDMiddle = 275.f; // cm, common local X plane between TPC outer wall and TRD layer 0 + o2::track::TrackParCov tpcOutCov; // local temporary with covariance (needed for chi2); persisted as TrackPar o2::track::TrackParCov trdInCov; - uint8_t trdInOK = 1; // default: no TRD match - float chi2PhiTPCTRD = -1.f; + uint8_t tpcOutOK = 255; // 0 propagated, 2 propagation failed, 255 switch off (not computed) + uint8_t trdInOK = 255; // 0 propagated, 1 not attempted (no TRD match or tpcOutOK != 0), 2 propagation failed, 255 switch off; count TRD matches with trdPattern > 0 + float chi2PhiTPCTRD = -1.f; // -1: not computed (switch off, trdInOK != 0) or singular covariance float chi2ZTPCTRD = -1.f; - if (itTRD != tpcToTRDMap.end() && itTRD->second.hasTRDTrackPar && tpcOutOK == 0) { - trdInCov = o2::track::TrackParCov(itTRD->second.trdTrackParCov); - // rotate to TPC alpha FIRST, then propagate — guarantees same frame + same X - if (trdInCov.rotate(tpcOutCov.getAlpha()) && - propagator->propagateTo(trdInCov, kTPCTRDMiddle, false, mMaxSnp, mCoarseStep, mMatType)) { - trdInOK = 0; - chi2PhiTPCTRD = chi2Match_Phi(tpcOutCov, trdInCov); - chi2ZTPCTRD = chi2Match_Z(tpcOutCov, trdInCov); - } else { - trdInOK = 2; // propagation failed + if (mStoreFullTrackPar) { + // D1: TPC outer → kTPCTRDMiddle + tpcOutCov = o2::track::TrackParCov(tracksTPC[iTrk].getParamOut()); + tpcOutOK = 2; // default: propagation failed + { + // rotate to sector angle at current position + float sn = std::sin(tpcOutCov.getAlpha()); + float cs = std::cos(tpcOutCov.getAlpha()); + float gy = tpcOutCov.getX() * sn + tpcOutCov.getY() * cs; + float gx = tpcOutCov.getX() * cs - tpcOutCov.getY() * sn; + if (tpcOutCov.rotate(std::atan2(gy, gx)) && + propagator->propagateTo(tpcOutCov, kTPCTRDMiddle, false, mMaxSnp, mCoarseStep, mMatType)) { + tpcOutOK = 0; + } + } + + // D2: TRD matched → kTPCTRDMiddle, rotated to SAME alpha as TPC out + trdInOK = 1; // default: not attempted + if (itTRD != tpcToTRDMap.end() && itTRD->second.hasTRDTrackPar && tpcOutOK == 0) { + trdInCov = o2::track::TrackParCov(itTRD->second.trdTrackParCov); + // rotate to TPC alpha FIRST, then propagate — guarantees same frame + same X + if (trdInCov.rotate(tpcOutCov.getAlpha()) && + propagator->propagateTo(trdInCov, kTPCTRDMiddle, false, mMaxSnp, mCoarseStep, mMatType)) { + trdInOK = 0; + chi2PhiTPCTRD = chi2Match_Phi(tpcOutCov, trdInCov); + chi2ZTPCTRD = chi2Match_Z(tpcOutCov, trdInCov); + } else { + trdInOK = 2; // propagation failed + } } } @@ -1609,6 +1623,29 @@ class TPCTimeSeries : public Task } double vertexTime = vertex.getTimeStamp().getTimeStamp(); double trackTime0 = trackFull.getTime0(); + // Optional payload (--store-full-trackpar, default off), persisted without covariance as TrackPar. + // This statement and the main chain below fill ONE entry: TreeStream keeps its element index until "\n", + // and the flag is constant for the job, so every entry has the same branch set (a varying set silently + // drops entries). Keep both statements adjacent, with no return/continue in between, and keep the objects in + // scope until the "\n" below (TreeStream stores pointers and reads them when the entry is filled). + // tpcAtVtx = TPC track after the DCA step; its reference depends on the path: + // all tracks: DCA to (0,0,0); + // then, for ITS-TPC vertex contributors passing the ITS-TPC DCA cuts: DCA to the primary vertex; + // then, if also passing the combined DCA cut and having an ITS track: rotated to the ITS track frame and + // propagated to the X of the ITS-TPC track. + // With the switch on, these three branches come first in the tree (compare by branch name, not position). + o2::track::TrackPar tpcOutPar; + o2::track::TrackPar trdInPar; + o2::track::TrackPar tpcAtVtxPar; + if (mStoreFullTrackPar) { + tpcOutPar = tpcOutCov; + trdInPar = trdInCov; + tpcAtVtxPar = track; + *mStreamer[iThread] << "treeTimeSeries" + << "tpcOut=" << tpcOutPar + << "trdIn=" << trdInPar + << "tpcAtVtx=" << tpcAtVtxPar; + } *mStreamer[iThread] << "treeTimeSeries" // DCAs << "triggerMask=" << triggerMask @@ -1656,15 +1693,14 @@ class TPCTimeSeries : public Task << "nTRDTracklets=" << nTRDTracklets << "trdTracklets=" << trdTrackletVec << "trdCalibTracklets=" << trdCalibVec - // Phase 0.4: TPC out / TRD in at R=275 cm - << "tpcOut=" << tpcOutCov + // TPC out / TRD in on the common local X = 275 cm plane: status and matching chi2 + // (255 / -1 when --store-full-trackpar is off; the TrackPar objects are written above) << "tpcOutOK=" << tpcOutOK - << "trdIn=" << trdInCov << "trdInOK=" << trdInOK << "chi2PhiTPCTRD=" << chi2PhiTPCTRD << "chi2ZTPCTRD=" << chi2ZTPCTRD - // Track parameters at vertex (full TrackParCov) - << "tpcAtVtx=" << track + // TRD in detector readout for this run (GRPECS): 1 yes, 0 no, 255 unknown + << "trdInReadout=" << mTRDInReadout << "chi2match_ITSTPC=" << chi2match_ITSTPC << "PID=" << trkOrig.getPID().getID() // TPC cov at vertex (without vertex constrained) @@ -2056,7 +2092,7 @@ o2::framework::DataProcessorSpec getTPCTimeSeriesSpec(const bool disableWriter, const bool enableAskMatLUT = matType == o2::base::Propagator::MatCorrType::USEMatCorrLUT; auto ccdbRequest = std::make_shared(!disableWriter, // orbitResetTime - false, // GRPECS=true for nHBF per TF + enableUnbinnedWriter, // GRPECS only when the unbinned trdInReadout can be written false, // GRPLHCIF true, // GRPMagField enableAskMatLUT, // askMatLUT @@ -2113,6 +2149,7 @@ o2::framework::DataProcessorSpec getTPCTimeSeriesSpec(const bool disableWriter, {"sample-unbinned-tsallis", VariantType::Bool, false, {"Perform sampling of unbinned data based on Tsallis function"}}, {"sampling-factor", VariantType::Float, 0.001f, {"Sampling factor in case sample-unbinned-tsallis is used"}}, {"disable-min-bias-trigger", VariantType::Bool, false, {"Disable the minimum bias trigger for skimmed data"}}, + {"store-full-trackpar", VariantType::Bool, false, {"Experimental: compute TPC-out/TRD-in states on the common local X = 275 cm plane and the TPC-TRD matching chi2, and store tpcOut, trdIn, tpcAtVtx as TrackPar in the unbinned tree"}}, {"out-file-unbinned", VariantType::String, "time_series_tracks.root", {"name of the output file for the unbinned data"}}, {"max-occupancy-bins", VariantType::Int, 912, {"Maximum number of occupancy bins"}}}}; } From 22f943e85e72a9b719ccaa38be0d05cf9028c14e Mon Sep 17 00:00:00 2001 From: miranov Date: Wed, 28 Oct 2026 08:17:36 +0100 Subject: [PATCH 4/9] TPC TimeSeries: store TRD-TPC residuals at the 275 cm plane by default - The TPC-out and TRD-in propagation to the common local X = 275 cm plane and the TPC-TRD matching chi2 now run for every written entry. - New branches deltaP0TRDTPC..deltaP4TRDTPC: TRD - TPC residuals of Y, Z, sin(phi), tan(lambda) and q/pT at that plane (same frame, same X), stored with float16-like precision (truncateFloatFraction, 10 mantissa bits); -999 if the TRD propagation was not done (trdInOK != 0). - --store-full-trackpar now controls only the tpcOut, trdIn and tpcAtVtx TrackPar objects; tpcOutOK/trdInOK no longer use the value 255. - Still open: the plane is local X, not the lab radius, and the TRD starting state is the TrackTRD base state. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01YYeB7SW1TM7vU8qhVHiG61 --- .../TPC/workflow/src/TPCTimeSeriesSpec.cxx | 99 +++++++++++-------- 1 file changed, 56 insertions(+), 43 deletions(-) diff --git a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx index 62f09229db45e..232a8fbcf55ef 100644 --- a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx +++ b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx @@ -30,6 +30,7 @@ #include "DetectorsCalibration/IntegratedClusterCalibrator.h" #include "CommonUtils/TreeStreamRedirector.h" #include "MathUtils/Tsallis.h" +#include "MathUtils/Utils.h" #include "ReconstructionDataFormats/TrackTPCITS.h" #include "ReconstructionDataFormats/PrimaryVertex.h" #include "ReconstructionDataFormats/VtxTrackIndex.h" @@ -71,7 +72,7 @@ class TPCTimeSeries : public Task uint8_t trdPattern = 0; uint8_t nTRDTracklets = 0; int trackletIndices[6] = {-1, -1, -1, -1, -1, -1}; - o2::track::TrackParCov trdTrackParCov; // TRD track state for propagation to the common local X = 275 cm plane; filled only with --store-full-trackpar + o2::track::TrackParCov trdTrackParCov; // TRD track state for propagation to the common local X = 275 cm plane bool hasTRDTrackPar = false; }; @@ -398,10 +399,8 @@ class TPCTimeSeries : public Task trdData.trackletIndices[iLay] = trkltId; } } - if (mStoreFullTrackPar) { - trdData.trdTrackParCov = o2::track::TrackParCov(trdTrack); // TrackTRD base state, not validated as the outer/entrance state (open Phase 0.4 finding) - trdData.hasTRDTrackPar = true; - } + trdData.trdTrackParCov = o2::track::TrackParCov(trdTrack); // TrackTRD base state, not validated as the outer/entrance state (open Phase 0.4 finding) + trdData.hasTRDTrackPar = true; tpcToTRDMap[refTPC] = trdData; } } @@ -1217,7 +1216,7 @@ class TPCTimeSeries : public Task float mMaxdEdxRegionRatio{0.5}; ///< maximum abs dedx region ratio: log(dedx_region/dedx) float mSamplingFactor{0.1}; ///< sampling factor in case sampling is used for unbinned data bool mSampleTsallis{false}; ///< perform sampling of unbinned data - bool mStoreFullTrackPar{false}; ///< experimental: compute and store tpcOut/trdIn/tpcAtVtx TrackPar and TPC-TRD chi2 (constant per job) + bool mStoreFullTrackPar{false}; ///< experimental: store tpcOut/trdIn/tpcAtVtx as TrackPar (constant per job) uint8_t mTRDInReadout{255}; ///< TRD in detector readout (GRPECS): 1 yes, 0 no, 255 unknown std::vector mGenerator; ///< random generator for debug tree sampling std::vector> mStreamer; ///< streamer for unbinned data @@ -1485,46 +1484,55 @@ class TPCTimeSeries : public Task } } - // Optional TPC-out / TRD-in states on a common local X plane (experimental, --store-full-trackpar, default off). + // TPC-out / TRD-in states on a common local X plane, computed for every written entry (Architect decision 2026-10-28: + // residuals always stored; only the full TrackPar objects depend on --store-full-trackpar). // Open Phase 0.4 findings, deferred: the target is local X = kTPCTRDMiddle in the frame rotated to the track // position angle (lab radius = sqrt(X^2 + Y^2), not exactly 275 cm), and the TRD starting state is the TrackTRD - // base state, not validated as the outer/entrance state. With the switch off nothing below is computed. - static constexpr float kTPCTRDMiddle = 275.f; // cm, common local X plane between TPC outer wall and TRD layer 0 - o2::track::TrackParCov tpcOutCov; // local temporary with covariance (needed for chi2); persisted as TrackPar + // base state (TrackTRD carries a single parameter set), not validated as the outer/entrance state. + static constexpr float kTPCTRDMiddle = 275.f; // cm, common local X plane between TPC outer wall and TRD layer 0 + static constexpr uint32_t kDeltaTRDTPCMask = 0xFFFFE000; // keep 10 mantissa bits: float16-like relative precision ~1e-3 + o2::track::TrackParCov tpcOutCov; // local temporary with covariance (needed for chi2); persisted as TrackPar o2::track::TrackParCov trdInCov; - uint8_t tpcOutOK = 255; // 0 propagated, 2 propagation failed, 255 switch off (not computed) - uint8_t trdInOK = 255; // 0 propagated, 1 not attempted (no TRD match or tpcOutOK != 0), 2 propagation failed, 255 switch off; count TRD matches with trdPattern > 0 - float chi2PhiTPCTRD = -1.f; // -1: not computed (switch off, trdInOK != 0) or singular covariance + uint8_t tpcOutOK = 2; // 0 propagated, 2 propagation failed + uint8_t trdInOK = 1; // 0 propagated, 1 not attempted (no TRD match or tpcOutOK != 0), 2 propagation failed; count TRD matches with trdPattern > 0 + float chi2PhiTPCTRD = -1.f; // -1: not computed (trdInOK != 0) or singular covariance float chi2ZTPCTRD = -1.f; - if (mStoreFullTrackPar) { - // D1: TPC outer → kTPCTRDMiddle - tpcOutCov = o2::track::TrackParCov(tracksTPC[iTrk].getParamOut()); - tpcOutOK = 2; // default: propagation failed - { - // rotate to sector angle at current position - float sn = std::sin(tpcOutCov.getAlpha()); - float cs = std::cos(tpcOutCov.getAlpha()); - float gy = tpcOutCov.getX() * sn + tpcOutCov.getY() * cs; - float gx = tpcOutCov.getX() * cs - tpcOutCov.getY() * sn; - if (tpcOutCov.rotate(std::atan2(gy, gx)) && - propagator->propagateTo(tpcOutCov, kTPCTRDMiddle, false, mMaxSnp, mCoarseStep, mMatType)) { - tpcOutOK = 0; - } + // TRD - TPC residuals at the common plane (same alpha, same X), parameters 0..4 = Y, Z, Snp, Tgl, Q2Pt; -999 if trdInOK != 0 + float deltaP0TRDTPC = -999.f; + float deltaP1TRDTPC = -999.f; + float deltaP2TRDTPC = -999.f; + float deltaP3TRDTPC = -999.f; + float deltaP4TRDTPC = -999.f; + // D1: TPC outer → kTPCTRDMiddle + tpcOutCov = o2::track::TrackParCov(tracksTPC[iTrk].getParamOut()); + { + // rotate to sector angle at current position + float sn = std::sin(tpcOutCov.getAlpha()); + float cs = std::cos(tpcOutCov.getAlpha()); + float gy = tpcOutCov.getX() * sn + tpcOutCov.getY() * cs; + float gx = tpcOutCov.getX() * cs - tpcOutCov.getY() * sn; + if (tpcOutCov.rotate(std::atan2(gy, gx)) && + propagator->propagateTo(tpcOutCov, kTPCTRDMiddle, false, mMaxSnp, mCoarseStep, mMatType)) { + tpcOutOK = 0; } + } - // D2: TRD matched → kTPCTRDMiddle, rotated to SAME alpha as TPC out - trdInOK = 1; // default: not attempted - if (itTRD != tpcToTRDMap.end() && itTRD->second.hasTRDTrackPar && tpcOutOK == 0) { - trdInCov = o2::track::TrackParCov(itTRD->second.trdTrackParCov); - // rotate to TPC alpha FIRST, then propagate — guarantees same frame + same X - if (trdInCov.rotate(tpcOutCov.getAlpha()) && - propagator->propagateTo(trdInCov, kTPCTRDMiddle, false, mMaxSnp, mCoarseStep, mMatType)) { - trdInOK = 0; - chi2PhiTPCTRD = chi2Match_Phi(tpcOutCov, trdInCov); - chi2ZTPCTRD = chi2Match_Z(tpcOutCov, trdInCov); - } else { - trdInOK = 2; // propagation failed - } + // D2: TRD matched → kTPCTRDMiddle, rotated to SAME alpha as TPC out + if (itTRD != tpcToTRDMap.end() && itTRD->second.hasTRDTrackPar && tpcOutOK == 0) { + trdInCov = o2::track::TrackParCov(itTRD->second.trdTrackParCov); + // rotate to TPC alpha FIRST, then propagate — guarantees same frame + same X + if (trdInCov.rotate(tpcOutCov.getAlpha()) && + propagator->propagateTo(trdInCov, kTPCTRDMiddle, false, mMaxSnp, mCoarseStep, mMatType)) { + trdInOK = 0; + chi2PhiTPCTRD = chi2Match_Phi(tpcOutCov, trdInCov); + chi2ZTPCTRD = chi2Match_Z(tpcOutCov, trdInCov); + deltaP0TRDTPC = o2::math_utils::truncateFloatFraction(trdInCov.getParam(0) - tpcOutCov.getParam(0), kDeltaTRDTPCMask); + deltaP1TRDTPC = o2::math_utils::truncateFloatFraction(trdInCov.getParam(1) - tpcOutCov.getParam(1), kDeltaTRDTPCMask); + deltaP2TRDTPC = o2::math_utils::truncateFloatFraction(trdInCov.getParam(2) - tpcOutCov.getParam(2), kDeltaTRDTPCMask); + deltaP3TRDTPC = o2::math_utils::truncateFloatFraction(trdInCov.getParam(3) - tpcOutCov.getParam(3), kDeltaTRDTPCMask); + deltaP4TRDTPC = o2::math_utils::truncateFloatFraction(trdInCov.getParam(4) - tpcOutCov.getParam(4), kDeltaTRDTPCMask); + } else { + trdInOK = 2; // propagation failed } } @@ -1693,12 +1701,17 @@ class TPCTimeSeries : public Task << "nTRDTracklets=" << nTRDTracklets << "trdTracklets=" << trdTrackletVec << "trdCalibTracklets=" << trdCalibVec - // TPC out / TRD in on the common local X = 275 cm plane: status and matching chi2 - // (255 / -1 when --store-full-trackpar is off; the TrackPar objects are written above) + // TPC out / TRD in on the common local X = 275 cm plane: status, matching chi2 and TRD - TPC residuals + // (the full TrackPar objects are written above, only with --store-full-trackpar) << "tpcOutOK=" << tpcOutOK << "trdInOK=" << trdInOK << "chi2PhiTPCTRD=" << chi2PhiTPCTRD << "chi2ZTPCTRD=" << chi2ZTPCTRD + << "deltaP0TRDTPC=" << deltaP0TRDTPC + << "deltaP1TRDTPC=" << deltaP1TRDTPC + << "deltaP2TRDTPC=" << deltaP2TRDTPC + << "deltaP3TRDTPC=" << deltaP3TRDTPC + << "deltaP4TRDTPC=" << deltaP4TRDTPC // TRD in detector readout for this run (GRPECS): 1 yes, 0 no, 255 unknown << "trdInReadout=" << mTRDInReadout << "chi2match_ITSTPC=" << chi2match_ITSTPC @@ -2149,7 +2162,7 @@ o2::framework::DataProcessorSpec getTPCTimeSeriesSpec(const bool disableWriter, {"sample-unbinned-tsallis", VariantType::Bool, false, {"Perform sampling of unbinned data based on Tsallis function"}}, {"sampling-factor", VariantType::Float, 0.001f, {"Sampling factor in case sample-unbinned-tsallis is used"}}, {"disable-min-bias-trigger", VariantType::Bool, false, {"Disable the minimum bias trigger for skimmed data"}}, - {"store-full-trackpar", VariantType::Bool, false, {"Experimental: compute TPC-out/TRD-in states on the common local X = 275 cm plane and the TPC-TRD matching chi2, and store tpcOut, trdIn, tpcAtVtx as TrackPar in the unbinned tree"}}, + {"store-full-trackpar", VariantType::Bool, false, {"Experimental: store tpcOut, trdIn (common local X = 275 cm plane) and tpcAtVtx as TrackPar in the unbinned tree"}}, {"out-file-unbinned", VariantType::String, "time_series_tracks.root", {"name of the output file for the unbinned data"}}, {"max-occupancy-bins", VariantType::Int, 912, {"Maximum number of occupancy bins"}}}}; } From 7f699d24470ccd379f923d96a78ced95ffd3dff5 Mon Sep 17 00:00:00 2001 From: miranov Date: Wed, 28 Oct 2026 09:16:42 +0100 Subject: [PATCH 5/9] TPC TimeSeries: store the TRD-TPC residuals as one TrackPar - deltaTRDTPC (o2::track::TrackPar) replaces deltaP0TRDTPC..deltaP4TRDTPC. mX and mAlpha are the common plane and frame in full precision (copied from the propagated TPC state); mP[0..4] are the TRD - TPC residuals of Y, Z, sin(phi), tan(lambda) and q/pT with 10 mantissa bits. Only getX(), getAlpha() and getParam(i) are meaningful on this object. - Default object (X = 0) if the TRD propagation was not done (trdInOK != 0). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01YYeB7SW1TM7vU8qhVHiG61 --- .../TPC/workflow/src/TPCTimeSeriesSpec.cxx | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx index 232a8fbcf55ef..7f5d5b3423145 100644 --- a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx +++ b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx @@ -1497,12 +1497,10 @@ class TPCTimeSeries : public Task uint8_t trdInOK = 1; // 0 propagated, 1 not attempted (no TRD match or tpcOutOK != 0), 2 propagation failed; count TRD matches with trdPattern > 0 float chi2PhiTPCTRD = -1.f; // -1: not computed (trdInOK != 0) or singular covariance float chi2ZTPCTRD = -1.f; - // TRD - TPC residuals at the common plane (same alpha, same X), parameters 0..4 = Y, Z, Snp, Tgl, Q2Pt; -999 if trdInOK != 0 - float deltaP0TRDTPC = -999.f; - float deltaP1TRDTPC = -999.f; - float deltaP2TRDTPC = -999.f; - float deltaP3TRDTPC = -999.f; - float deltaP4TRDTPC = -999.f; + // deltaTRDTPC: TrackPar used as a residual container. mX and mAlpha = the common plane and frame (full precision, + // copied from the propagated TPC state); mP[0..4] = TRD - TPC of Y, Z, Snp, Tgl, Q2Pt, truncated to 10 mantissa bits. + // Only getX(), getAlpha() and getParam(i) are meaningful (getPt(), getPhi(), ... are not). Default (X = 0) if trdInOK != 0. + o2::track::TrackPar deltaTRDTPC; // D1: TPC outer → kTPCTRDMiddle tpcOutCov = o2::track::TrackParCov(tracksTPC[iTrk].getParamOut()); { @@ -1526,11 +1524,10 @@ class TPCTimeSeries : public Task trdInOK = 0; chi2PhiTPCTRD = chi2Match_Phi(tpcOutCov, trdInCov); chi2ZTPCTRD = chi2Match_Z(tpcOutCov, trdInCov); - deltaP0TRDTPC = o2::math_utils::truncateFloatFraction(trdInCov.getParam(0) - tpcOutCov.getParam(0), kDeltaTRDTPCMask); - deltaP1TRDTPC = o2::math_utils::truncateFloatFraction(trdInCov.getParam(1) - tpcOutCov.getParam(1), kDeltaTRDTPCMask); - deltaP2TRDTPC = o2::math_utils::truncateFloatFraction(trdInCov.getParam(2) - tpcOutCov.getParam(2), kDeltaTRDTPCMask); - deltaP3TRDTPC = o2::math_utils::truncateFloatFraction(trdInCov.getParam(3) - tpcOutCov.getParam(3), kDeltaTRDTPCMask); - deltaP4TRDTPC = o2::math_utils::truncateFloatFraction(trdInCov.getParam(4) - tpcOutCov.getParam(4), kDeltaTRDTPCMask); + deltaTRDTPC = tpcOutCov; // mX, mAlpha (and charge, PID) of the common plane + for (int iPar = 0; iPar < 5; ++iPar) { + deltaTRDTPC.setParam(o2::math_utils::truncateFloatFraction(trdInCov.getParam(iPar) - tpcOutCov.getParam(iPar), kDeltaTRDTPCMask), iPar); + } } else { trdInOK = 2; // propagation failed } @@ -1707,11 +1704,7 @@ class TPCTimeSeries : public Task << "trdInOK=" << trdInOK << "chi2PhiTPCTRD=" << chi2PhiTPCTRD << "chi2ZTPCTRD=" << chi2ZTPCTRD - << "deltaP0TRDTPC=" << deltaP0TRDTPC - << "deltaP1TRDTPC=" << deltaP1TRDTPC - << "deltaP2TRDTPC=" << deltaP2TRDTPC - << "deltaP3TRDTPC=" << deltaP3TRDTPC - << "deltaP4TRDTPC=" << deltaP4TRDTPC + << "deltaTRDTPC=" << deltaTRDTPC // TRD in detector readout for this run (GRPECS): 1 yes, 0 no, 255 unknown << "trdInReadout=" << mTRDInReadout << "chi2match_ITSTPC=" << chi2match_ITSTPC From ed835a219dfb9f44a2c286aa16b250b9a599a0ee Mon Sep 17 00:00:00 2001 From: miranov Date: Wed, 28 Oct 2026 14:16:48 +0100 Subject: [PATCH 6/9] TPC TimeSeries: use the outer TRD state for the TRD-TPC residuals - The TRD side of deltaTRDTPC, of the matching chi2 and of trdIn is now the outer state of the ITS-TPC-TRD track (TrackTRD::getOuterParam(): outward refit through ITS, TPC and TRD, at the outermost TRD layer), propagated inward to the 275 cm plane, as RecoContainer does. The base state used before is the inward refit, which ends at the innermost ITS cluster, i.e. the combined track at the vertex. - Comments corrected: TrackTRD has two parameter sets; the outward refit includes the TPC clusters, so TRD - TPC is not independent; full list of meaningful getters of deltaTRDTPC. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01YYeB7SW1TM7vU8qhVHiG61 --- Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx index 7f5d5b3423145..f846a32457c54 100644 --- a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx +++ b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx @@ -399,7 +399,9 @@ class TPCTimeSeries : public Task trdData.trackletIndices[iLay] = trkltId; } } - trdData.trdTrackParCov = o2::track::TrackParCov(trdTrack); // TrackTRD base state, not validated as the outer/entrance state (open Phase 0.4 finding) + // outer state of the ITS-TPC-TRD track: outward refit through ITS, TPC and TRD, at the outermost TRD layer (as in + // RecoContainer::getTrackParamOut). The base state of TrackTRD is the inward refit, which ends at the innermost ITS cluster. + trdData.trdTrackParCov = trdTrack.getOuterParam(); trdData.hasTRDTrackPar = true; tpcToTRDMap[refTPC] = trdData; } @@ -1486,9 +1488,9 @@ class TPCTimeSeries : public Task // TPC-out / TRD-in states on a common local X plane, computed for every written entry (Architect decision 2026-10-28: // residuals always stored; only the full TrackPar objects depend on --store-full-trackpar). - // Open Phase 0.4 findings, deferred: the target is local X = kTPCTRDMiddle in the frame rotated to the track - // position angle (lab radius = sqrt(X^2 + Y^2), not exactly 275 cm), and the TRD starting state is the TrackTRD - // base state (TrackTRD carries a single parameter set), not validated as the outer/entrance state. + // Open Phase 0.4 finding, deferred: the target is local X = kTPCTRDMiddle in the frame rotated to the track position + // angle (lab radius = sqrt(X^2 + Y^2), not exactly 275 cm). The TRD side is the outer state of the ITS-TPC-TRD track, + // propagated inward to the plane; its outward refit includes the TPC clusters, so TRD - TPC is not independent. static constexpr float kTPCTRDMiddle = 275.f; // cm, common local X plane between TPC outer wall and TRD layer 0 static constexpr uint32_t kDeltaTRDTPCMask = 0xFFFFE000; // keep 10 mantissa bits: float16-like relative precision ~1e-3 o2::track::TrackParCov tpcOutCov; // local temporary with covariance (needed for chi2); persisted as TrackPar @@ -1499,7 +1501,9 @@ class TPCTimeSeries : public Task float chi2ZTPCTRD = -1.f; // deltaTRDTPC: TrackPar used as a residual container. mX and mAlpha = the common plane and frame (full precision, // copied from the propagated TPC state); mP[0..4] = TRD - TPC of Y, Z, Snp, Tgl, Q2Pt, truncated to 10 mantissa bits. - // Only getX(), getAlpha() and getParam(i) are meaningful (getPt(), getPhi(), ... are not). Default (X = 0) if trdInOK != 0. + // Meaningful: getX(), getAlpha(), getParam(i), getY(), getZ(), getSnp(), getTgl(), getQ2Pt(); not meaningful: derived + // getters (getPt(), getP(), getPhi(), getEta(), getCurvature(), ...). Charge, PID and user field are those of the TPC + // track. Default object (X = 0, parameters 0) if trdInOK != 0: select with trdInOK == 0. o2::track::TrackPar deltaTRDTPC; // D1: TPC outer → kTPCTRDMiddle tpcOutCov = o2::track::TrackParCov(tracksTPC[iTrk].getParamOut()); From 48a37e7621a0e4a9f97d621edd18cf6d0559fe3d Mon Sep 17 00:00:00 2001 From: miranov Date: Wed, 28 Oct 2026 15:55:32 +0100 Subject: [PATCH 7/9] TPC TimeSeries: TRD read out in the collision, vertex contributor counts - TRD-triggered BCs are collected once per TF from the TRD trigger records (bogus bc >= 3564 dropped). Per entry, trdTrigDBCBefore/After give the closest TRD trigger to the BC range [IRMin, IRMax] of the entry's vertex: both 0 if a trigger lies inside the range, else the distance in BC before the range start (< 0) and after its end (> 0); -999999/999999 if none or no vertex BC range. - vertex_nContributorsITS/ITSTPC/TRD: contributor counts of the same vertex, so the TRD-matched fraction of high-multiplicity collisions gives a second, independent TRD-readout estimator. - The TRD-out state is taken with recoData.getTrackParamOut(gid). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01YYeB7SW1TM7vU8qhVHiG61 --- .../TPC/workflow/src/TPCTimeSeriesSpec.cxx | 81 ++++++++++++++++++- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx index f846a32457c54..6392eb9cebd97 100644 --- a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx +++ b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx @@ -45,6 +45,8 @@ #include "DataFormatsTRD/TrackTRD.h" #include "DataFormatsTRD/Tracklet64.h" #include "DataFormatsTRD/CalibratedTracklet.h" +#include "DataFormatsTRD/TriggerRecord.h" +#include "CommonConstants/LHCConstants.h" #include "TROOT.h" #include "ReconstructionDataFormats/MatchInfoTOF.h" #include "DataFormatsTOF/Cluster.h" @@ -376,6 +378,20 @@ class TPCTimeSeries : public Task std::unordered_map tpcToTRDMap; auto trdTracklets = (mTPCOnly || !recoData.inputsTRD) ? gsl::span() : recoData.getTRDTracklets(); auto trdCalibTracklets = (mTPCOnly || !recoData.inputsTRD) ? gsl::span() : recoData.getTRDCalibratedTracklets(); + + // TRD-triggered BCs of this TF: TRD is read out only at these BCs. Built once per TF; per entry only the triggers closest + // to the collision are stored (trdTrigDBCBefore/After). Records with bc >= LHCMaxBunches are dropped, as in the AOD producer. + mTRDTrigBCs.clear(); + if (mUnbinnedWriter && !mTPCOnly && recoData.inputsTRD) { + const auto trdTrig = recoData.getTRDTriggerRecords(); + mTRDTrigBCs.reserve(trdTrig.size()); + for (const auto& trg : trdTrig) { + if (trg.getBCData().bc < o2::constants::lhc::LHCMaxBunches) { + mTRDTrigBCs.push_back(trg.getBCData()); + } + } + std::sort(mTRDTrigBCs.begin(), mTRDTrigBCs.end()); + } if (mUnbinnedWriter && !mTPCOnly) { // scan ITS-TPC-TRD tracks auto itstpctrdTracks = recoData.getITSTPCTRDTracks(); @@ -399,9 +415,9 @@ class TPCTimeSeries : public Task trdData.trackletIndices[iLay] = trkltId; } } - // outer state of the ITS-TPC-TRD track: outward refit through ITS, TPC and TRD, at the outermost TRD layer (as in - // RecoContainer::getTrackParamOut). The base state of TrackTRD is the inward refit, which ends at the innermost ITS cluster. - trdData.trdTrackParCov = trdTrack.getOuterParam(); + // TRD-out: outer state of the ITS-TPC-TRD track (outward refit through ITS, TPC and TRD, at the outermost TRD layer). + // The base state of TrackTRD is the inward refit, which ends at the innermost ITS cluster. + trdData.trdTrackParCov = recoData.getTrackParamOut(gid); trdData.hasTRDTrackPar = true; tpcToTRDMap[refTPC] = trdData; } @@ -1220,6 +1236,10 @@ class TPCTimeSeries : public Task bool mSampleTsallis{false}; ///< perform sampling of unbinned data bool mStoreFullTrackPar{false}; ///< experimental: store tpcOut/trdIn/tpcAtVtx as TrackPar (constant per job) uint8_t mTRDInReadout{255}; ///< TRD in detector readout (GRPECS): 1 yes, 0 no, 255 unknown + std::vector mTRDTrigBCs; ///< TRD-triggered BCs of the current TF, sorted + std::vector mNContributorsITS; ///< per vertex ID: ITS-only PV contributors (current TF) + std::vector mNContributorsITSTPC; ///< per vertex ID: ITS-TPC PV contributors, TRD/TOF-matched included (current TF) + std::vector mNContributorsTRD; ///< per vertex ID: ITS-TPC-TRD (and -TOF) PV contributors (current TF) std::vector mGenerator; ///< random generator for debug tree sampling std::vector> mStreamer; ///< streamer for unbinned data float mXOuterMatching{60}; ///< reference x where delta track parameters for TPC with ITS-TPC track are stored in skimmed data @@ -1310,6 +1330,11 @@ class TPCTimeSeries : public Task // get vertex (check if vertex ID is valid). In case no vertex is assigned return nearest vertex or else default vertex const auto vertex = (idxITSTPC.back() != -1) ? vertices[idxITSTPC.back()] : ((mNearestVtxTPC[iTrk] != -1) ? vertices[mNearestVtxTPC[iTrk]] : o2::dataformats::PrimaryVertex{}); + // index of that vertex (-1: none) and its contributor counts, -1 without vertex + const int vtxID = (idxITSTPC.back() != -1) ? idxITSTPC.back() : mNearestVtxTPC[iTrk]; + const int nContribITS = (vtxID >= 0 && vtxID < static_cast(mNContributorsITS.size())) ? mNContributorsITS[vtxID] : -1; + const int nContribITSTPC = (vtxID >= 0 && vtxID < static_cast(mNContributorsITSTPC.size())) ? mNContributorsITSTPC[vtxID] : -1; + const int nContribTRD = (vtxID >= 0 && vtxID < static_cast(mNContributorsTRD.size())) ? mNContributorsTRD[vtxID] : -1; // calculate DCAz: (time TPC track - time vertex) * vDrift + sign_side * vertexZ const float signSide = trackFull.hasCSideClustersOnly() ? -1 : 1; // invert sign for C-side @@ -1537,6 +1562,29 @@ class TPCTimeSeries : public Task } } + // TRD read out in the collision of this entry: TRD-triggered BCs closest to the BC range [IRMin, IRMax] of the entry's + // vertex. Both 0: a TRD trigger inside the range. Otherwise trdTrigDBCBefore < 0 = BCs from the range start back to the + // closest earlier trigger, trdTrigDBCAfter > 0 = BCs from the range end to the closest later trigger. Sentinels -999999 / + // 999999: no vertex with a BC range, or no such trigger in this TF. + int trdTrigDBCBefore = -999999; + int trdTrigDBCAfter = 999999; + if (vtxID >= 0 && !vertex.getIRMin().isDummy() && !vertex.getIRMax().isDummy()) { + const auto& irMin = vertex.getIRMin(); + const auto& irMax = vertex.getIRMax(); + const auto itTrig = std::lower_bound(mTRDTrigBCs.begin(), mTRDTrigBCs.end(), irMin); // first trigger at or after irMin + if (itTrig != mTRDTrigBCs.end() && *itTrig <= irMax) { + trdTrigDBCBefore = 0; + trdTrigDBCAfter = 0; + } else { + if (itTrig != mTRDTrigBCs.end()) { + trdTrigDBCAfter = static_cast(itTrig->differenceInBC(irMax)); + } + if (itTrig != mTRDTrigBCs.begin()) { + trdTrigDBCBefore = static_cast((itTrig - 1)->differenceInBC(irMin)); + } + } + } + int typeSide = 2; // A- and C-Side cluster if (trackFull.hasASideClustersOnly()) { typeSide = 0; @@ -1676,6 +1724,12 @@ class TPCTimeSeries : public Task << "vertex_z=" << vertex.getZ() << "vertex_time=" << vertex.getTimeStamp().getTimeStamp() << "vertex_nContributors=" << vertex.getNContributors() + // contributor counts of the same vertex (-1 without vertex) and TRD triggers closest to its BC range + << "vertex_nContributorsITS=" << nContribITS + << "vertex_nContributorsITSTPC=" << nContribITSTPC + << "vertex_nContributorsTRD=" << nContribTRD + << "trdTrigDBCBefore=" << trdTrigDBCBefore + << "trdTrigDBCAfter=" << trdTrigDBCAfter << "isNearestVertex=" << isNearestVtx // tpc track properties << "pt=" << trkOrig.getPt() @@ -1937,6 +1991,27 @@ class TPCTimeSeries : public Task } } + // per-vertex contributor counts, also written per entry of the unbinned tree (vertex_nContributorsITS/ITSTPC/TRD) + mNContributorsITS.assign(vertices.size(), 0); + mNContributorsITSTPC.assign(vertices.size(), 0); + mNContributorsTRD.assign(vertices.size(), 0); + const int nVertices = vertices.size(); + for (const auto& [vID, n] : nContributors_ITS) { + if (vID >= 0 && vID < nVertices) { + mNContributorsITS[vID] = n; + } + } + for (const auto& [vID, n] : nContributors_ITSTPC) { + if (vID >= 0 && vID < nVertices) { + mNContributorsITSTPC[vID] = n; + } + } + for (const auto& [vID, n] : nContributors_TRD) { + if (vID >= 0 && vID < nVertices) { + mNContributorsTRD[vID] = n; + } + } + // calculate statistics std::array avgVtxITS; std::array avgVtxITSTPC; From 514d29bab9838ee1bbdd231f78bf7c05958266d2 Mon Sep 17 00:00:00 2001 From: miranov Date: Wed, 28 Oct 2026 16:19:31 +0100 Subject: [PATCH 8/9] TPC TimeSeries: BC numbers of the vertex and of the closest TRD triggers - vertex_bc: BC number (from the TF start, orbit firstTFOrbit) of the vertex time, for the same vertex as vertex_time. - trdTrigBCBefore/After: BC numbers of the closest TRD-triggered BCs at or before / at or after vertex_bc (-999999 if none or no vertex), so the TRD-in-collision estimators can be calibrated from raw BC numbers. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01YYeB7SW1TM7vU8qhVHiG61 --- .../TPC/workflow/src/TPCTimeSeriesSpec.cxx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx index 6392eb9cebd97..b541e9d98f27f 100644 --- a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx +++ b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx @@ -382,6 +382,7 @@ class TPCTimeSeries : public Task // TRD-triggered BCs of this TF: TRD is read out only at these BCs. Built once per TF; per entry only the triggers closest // to the collision are stored (trdTrigDBCBefore/After). Records with bc >= LHCMaxBunches are dropped, as in the AOD producer. mTRDTrigBCs.clear(); + mTRDTrigBCsInTF.clear(); if (mUnbinnedWriter && !mTPCOnly && recoData.inputsTRD) { const auto trdTrig = recoData.getTRDTriggerRecords(); mTRDTrigBCs.reserve(trdTrig.size()); @@ -427,6 +428,12 @@ class TPCTimeSeries : public Task if (mUnbinnedWriter) { mTPCTrackClIdx = pc.inputs().get>("trackTPCClRefs"); mFirstTFOrbit = processing_helpers::getFirstTForbit(pc); + // TRD-triggered BCs as BC numbers from the TF start (orbit mFirstTFOrbit, bc 0), in the order of mTRDTrigBCs (sorted) + const o2::InteractionRecord irTFStart(0, mFirstTFOrbit); + mTRDTrigBCsInTF.reserve(mTRDTrigBCs.size()); + for (const auto& ir : mTRDTrigBCs) { + mTRDTrigBCsInTF.push_back(static_cast(ir.differenceInBC(irTFStart))); + } } // get local multiplicity - count neighbouring tracks @@ -1237,6 +1244,7 @@ class TPCTimeSeries : public Task bool mStoreFullTrackPar{false}; ///< experimental: store tpcOut/trdIn/tpcAtVtx as TrackPar (constant per job) uint8_t mTRDInReadout{255}; ///< TRD in detector readout (GRPECS): 1 yes, 0 no, 255 unknown std::vector mTRDTrigBCs; ///< TRD-triggered BCs of the current TF, sorted + std::vector mTRDTrigBCsInTF; ///< the same BCs as BC numbers from the TF start (orbit mFirstTFOrbit) std::vector mNContributorsITS; ///< per vertex ID: ITS-only PV contributors (current TF) std::vector mNContributorsITSTPC; ///< per vertex ID: ITS-TPC PV contributors, TRD/TOF-matched included (current TF) std::vector mNContributorsTRD; ///< per vertex ID: ITS-TPC-TRD (and -TOF) PV contributors (current TF) @@ -1585,6 +1593,25 @@ class TPCTimeSeries : public Task } } + // BC numbers from the TF start (orbit firstTFOrbit, bc 0), for calibrating the two estimators: BC of the vertex time + // (same reference as vertex_time), and the closest TRD-triggered BCs at or before and at or after it (both equal to + // vertexBC if a trigger is at that BC). -999999 if no vertex or no such trigger in this TF. + int vertexBC = -999999; + int trdTrigBCBefore = -999999; + int trdTrigBCAfter = -999999; + if (vtxID >= 0) { + vertexBC = static_cast(std::lround(vertex.getTimeStamp().getTimeStamp() / o2::constants::lhc::LHCBunchSpacingMUS)); + const auto itBC = std::lower_bound(mTRDTrigBCsInTF.begin(), mTRDTrigBCsInTF.end(), vertexBC); // first trigger at or after vertexBC + if (itBC != mTRDTrigBCsInTF.end()) { + trdTrigBCAfter = *itBC; + } + if (itBC != mTRDTrigBCsInTF.end() && *itBC == vertexBC) { + trdTrigBCBefore = vertexBC; + } else if (itBC != mTRDTrigBCsInTF.begin()) { + trdTrigBCBefore = *(itBC - 1); + } + } + int typeSide = 2; // A- and C-Side cluster if (trackFull.hasASideClustersOnly()) { typeSide = 0; @@ -1730,6 +1757,10 @@ class TPCTimeSeries : public Task << "vertex_nContributorsTRD=" << nContribTRD << "trdTrigDBCBefore=" << trdTrigDBCBefore << "trdTrigDBCAfter=" << trdTrigDBCAfter + // BC numbers from the TF start: vertex (from its time) and closest TRD triggers at or before / at or after it + << "vertex_bc=" << vertexBC + << "trdTrigBCBefore=" << trdTrigBCBefore + << "trdTrigBCAfter=" << trdTrigBCAfter << "isNearestVertex=" << isNearestVtx // tpc track properties << "pt=" << trkOrig.getPt() From a715df6e5f8a9a94b1df0cb99bae0809a0d33b18 Mon Sep 17 00:00:00 2001 From: miranov25 Date: Wed, 28 Oct 2026 16:26:34 +0100 Subject: [PATCH 9/9] TPC TimeSeries: vertex BC and BC differences to the closest TRD triggers - vertex_bc: BC number (from the TF start, orbit firstTFOrbit) of the vertex time, for the same vertex as vertex_time. - trdTrigDBCVtxBefore/After: closest TRD-triggered BC at or before / at or after vertex_bc, minus vertex_bc (<= 0 / >= 0; -999999/999999 if none or no vertex), to calibrate the TRD-in-collision estimators. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01YYeB7SW1TM7vU8qhVHiG61 --- .../TPC/workflow/src/TPCTimeSeriesSpec.cxx | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx index b541e9d98f27f..89b74d05dbcf4 100644 --- a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx +++ b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx @@ -1593,22 +1593,23 @@ class TPCTimeSeries : public Task } } - // BC numbers from the TF start (orbit firstTFOrbit, bc 0), for calibrating the two estimators: BC of the vertex time - // (same reference as vertex_time), and the closest TRD-triggered BCs at or before and at or after it (both equal to - // vertexBC if a trigger is at that BC). -999999 if no vertex or no such trigger in this TF. + // For calibrating the two estimators: BC of the vertex time as a BC number from the TF start (orbit firstTFOrbit, bc 0; + // same reference as vertex_time), and the closest TRD-triggered BCs at or before / at or after it, as BC differences to + // vertex_bc: trdTrigDBCVtxBefore <= 0, trdTrigDBCVtxAfter >= 0 (both 0 if a trigger is at vertex_bc). + // Sentinels: vertex_bc -999999 without vertex; -999999 / 999999 without vertex or without such a trigger in this TF. int vertexBC = -999999; - int trdTrigBCBefore = -999999; - int trdTrigBCAfter = -999999; + int trdTrigDBCVtxBefore = -999999; + int trdTrigDBCVtxAfter = 999999; if (vtxID >= 0) { vertexBC = static_cast(std::lround(vertex.getTimeStamp().getTimeStamp() / o2::constants::lhc::LHCBunchSpacingMUS)); const auto itBC = std::lower_bound(mTRDTrigBCsInTF.begin(), mTRDTrigBCsInTF.end(), vertexBC); // first trigger at or after vertexBC if (itBC != mTRDTrigBCsInTF.end()) { - trdTrigBCAfter = *itBC; + trdTrigDBCVtxAfter = *itBC - vertexBC; } if (itBC != mTRDTrigBCsInTF.end() && *itBC == vertexBC) { - trdTrigBCBefore = vertexBC; + trdTrigDBCVtxBefore = 0; } else if (itBC != mTRDTrigBCsInTF.begin()) { - trdTrigBCBefore = *(itBC - 1); + trdTrigDBCVtxBefore = *(itBC - 1) - vertexBC; } } @@ -1757,10 +1758,10 @@ class TPCTimeSeries : public Task << "vertex_nContributorsTRD=" << nContribTRD << "trdTrigDBCBefore=" << trdTrigDBCBefore << "trdTrigDBCAfter=" << trdTrigDBCAfter - // BC numbers from the TF start: vertex (from its time) and closest TRD triggers at or before / at or after it + // vertex BC (from its time, BC number from the TF start) and closest TRD triggers as BC differences to it << "vertex_bc=" << vertexBC - << "trdTrigBCBefore=" << trdTrigBCBefore - << "trdTrigBCAfter=" << trdTrigBCAfter + << "trdTrigDBCVtxBefore=" << trdTrigDBCVtxBefore + << "trdTrigDBCVtxAfter=" << trdTrigDBCVtxAfter << "isNearestVertex=" << isNearestVtx // tpc track properties << "pt=" << trkOrig.getPt()