Skip to content

Commit db83481

Browse files
[Common] Fix TPC side determination in VDrift correction (#17846)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 8e36596 commit db83481

1 file changed

Lines changed: 61 additions & 3 deletions

File tree

Common/Core/TPCVDriftManager.h

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,18 @@ class TPCVDriftManager
3737
mCCDB = ccdb;
3838
}
3939

40+
// Use the TPC side flags (with legacy-data fallback) instead of the tgl-sign-based correction.
41+
// Off by default so that existing analyses see no change in results until this is explicitly
42+
// enabled for testing. Tasks using TPCVDriftManager can expose this via their own Configurable.
43+
void setUseSideBasedCorrection(bool value) noexcept
44+
{
45+
mUseSideBasedCorrection = value;
46+
}
47+
4048
void update(uint64_t timestamp) noexcept
4149
{
4250
// Keep the object we already have if it is still valid for this timestamp.
43-
// firstTime/lastTime are the first and last timestamps of the TFs the correction
51+
// firstTime/lastTime are the first and last timestamps of the TF the correction
4452
// was derived from, so the validity range is closed on both ends.
4553
if (mVD != nullptr && timestamp >= static_cast<uint64_t>(mVD->firstTime) && timestamp <= static_cast<uint64_t>(mVD->lastTime)) {
4654
return;
@@ -136,7 +144,7 @@ class TPCVDriftManager
136144
float dTime = tTB - trackExtra.trackTime();
137145
float dDrift = dTime * mTPCVDriftNS;
138146
float dDriftErr = tTBErr * mTPCVDriftNS;
139-
if (dDriftErr < 0.f || dDrift > 250.f) { // we cannot move a track outside the drift volume
147+
if (dDriftErr < 0.f || dDrift > mMaxDriftCm) { // we cannot move a track outside the drift volume
140148
if (mOutside < mWarningLimit) {
141149
LOGP(warn, "Skipping correction outside of tpc volume with dDrift={} +- {}", dDrift, dDriftErr);
142150
const auto trackBC = trackExtra.template collision_as<Collisions>().template foundBC_as<BCs>().globalBC();
@@ -152,7 +160,54 @@ class TPCVDriftManager
152160
}
153161

154162
// impose new Z coordinate
155-
track.setZ(track.getZ() + ((track.getTgl() < 0.) ? -dDrift : dDrift));
163+
float zShift = 0.f;
164+
if (!mUseSideBasedCorrection) {
165+
// Legacy behaviour (default): infer the side from tgl alone.
166+
zShift = (track.getTgl() < 0.f) ? -dDrift : dDrift;
167+
} else {
168+
const auto sides = (trackExtra.flags() & (o2::aod::track::TrackFlags::TPCSideA | o2::aod::track::TrackFlags::TPCSideC));
169+
if (sides == o2::aod::track::TrackFlags::TPCSideA) {
170+
zShift = dDrift;
171+
} else if (sides == o2::aod::track::TrackFlags::TPCSideC) {
172+
zShift = -dDrift;
173+
} else if (sides == 0) {
174+
// Fallback for datasets produced before the TPC side flags were introduced (Feb. 2026).
175+
o2::aod::track::extensions::TPCTimeErrEncoding tEnc;
176+
tEnc.encoding.timeErr = trackExtra.trackTimeRes();
177+
const float dFwd = tEnc.getDeltaTFwd();
178+
const float dBwd = tEnc.getDeltaTBwd();
179+
// Equal, small forward/backward margins mean the track is bounded on both ends,
180+
// i.e. it crosses the CE: it cannot be moved and is already corrected elsewhere.
181+
const bool crossesCE = (dFwd == dBwd) && (dFwd < mMaxCECrossingDeltaTNS);
182+
if (!crossesCE) {
183+
const bool zPositive = track.getZ() > 0.f;
184+
const bool tglPositive = track.getTgl() > 0.f;
185+
int side = 0; // +1 = A, -1 = C, 0 = undetermined -> leave uncorrected
186+
if (zPositive == tglPositive) {
187+
// Consistent sign: the track converges to Z=0 at the beamline by construction.
188+
side = tglPositive ? 1 : -1;
189+
} else if (dBwd == 0.f && dFwd > 0.f) {
190+
// Bounded backward at the CE with room forward: no clusters on the opposite
191+
// side, so trust the measured Z rather than the (here inverted) tgl.
192+
side = zPositive ? 1 : -1;
193+
} else if (dBwd > 0.f) {
194+
// Large tgl track bounded at the readout side instead: trust tgl.
195+
side = tglPositive ? 1 : -1;
196+
}
197+
// else: degenerate case, track touches both CE and readout -> cannot be deduced/moved.
198+
// (in practice unreachable here: dFwd==dBwd==0 would already satisfy crossesCE above,
199+
// since dFwd/dBwd are always >= 0; kept explicit to mirror the reference logic 1:1.)
200+
201+
if (side > 0) {
202+
zShift = dDrift;
203+
} else if (side < 0) {
204+
zShift = -dDrift;
205+
}
206+
}
207+
}
208+
// else: track has clusters on both sides (crossed the CE) and is already corrected elsewhere
209+
}
210+
track.setZ(track.getZ() + zShift);
156211
if constexpr (std::is_base_of_v<o2::track::TrackParCov, Track>) {
157212
track.setCov(track.getSigmaZ2() + dDriftErr * dDriftErr, o2::track::kSigZ2);
158213
}
@@ -169,6 +224,7 @@ class TPCVDriftManager
169224

170225
private:
171226
bool mValid{false};
227+
bool mUseSideBasedCorrection{false}; // off by default: preserves legacy tgl-sign behaviour
172228
// Factors
173229
float mTPCVDriftNS{0.f}; // drift velocity in cm/ns
174230

@@ -177,6 +233,8 @@ class TPCVDriftManager
177233
o2::ccdb::BasicCCDBManager* mCCDB{}; // reference to initialized ccdb manager
178234

179235
static constexpr unsigned int mWarningLimit{10};
236+
static constexpr float mMaxDriftCm{250.f}; // TPC drift volume half-length in cm
237+
static constexpr float mMaxCECrossingDeltaTNS{1000.f}; // ~1 us, ballpark forward/backward time margin of a CE-crossing track
180238

181239
// Counters
182240
unsigned int mCalls{0}; // total number of calls

0 commit comments

Comments
 (0)