Skip to content

Commit ec64032

Browse files
[Common] Add global switch for TPC side-based VDrift correction
Introduce TPCVDriftManagerParam::useSideBasedCorrection (default off) to gate the new TPC-side-flag-based correction behind a ConfigurableParam, settable from any workflow via --configKeyValues without touching each task that owns a TPCVDriftManager instance. Keeps existing analyses on the legacy tgl-sign behaviour until explicitly opted in for testing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent be7148b commit ec64032

1 file changed

Lines changed: 54 additions & 38 deletions

File tree

Common/Core/TPCVDriftManager.h

Lines changed: 54 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
#include <CCDB/BasicCCDBManager.h>
1616
#include <CommonConstants/LHCConstants.h>
17+
#include <CommonUtils/ConfigurableParam.h>
18+
#include <CommonUtils/ConfigurableParamHelper.h>
1719
#include <DataFormatsTPC/VDriftCorrFact.h>
1820
#include <Framework/DataTypes.h>
1921
#include <Framework/Logger.h>
@@ -26,6 +28,15 @@
2628
namespace o2::aod::common
2729
{
2830

31+
struct TPCVDriftManagerParam : public o2::conf::ConfigurableParamHelper<TPCVDriftManagerParam> {
32+
// Use the TPC side flags (with legacy-data fallback) instead of the tgl-sign-based correction.
33+
// Off by default so that existing analyses see no change in results until this is explicitly
34+
// enabled for testing.
35+
bool useSideBasedCorrection = false;
36+
37+
O2ParamDef(TPCVDriftManagerParam, "TPCVDriftManager");
38+
};
39+
2940
// Thin wrapper for vdrift ccdb queries should partially mirror VDriftHelper class.
3041
// Allows to move TPC standalone tracks under the assumption of a different
3142
// collision than the track is associated to.
@@ -131,48 +142,53 @@ class TPCVDriftManager
131142
}
132143

133144
// impose new Z coordinate
134-
const auto sides = (trackExtra.flags() & (o2::aod::track::TrackFlags::TPCSideA | o2::aod::track::TrackFlags::TPCSideC));
135145
float zShift = 0.f;
136-
if (sides == o2::aod::track::TrackFlags::TPCSideA) {
137-
zShift = dDrift;
138-
} else if (sides == o2::aod::track::TrackFlags::TPCSideC) {
139-
zShift = -dDrift;
140-
} else if (sides == 0) {
141-
// Fallback for datasets produced before the TPC side flags were introduced (Feb. 2026).
142-
o2::aod::track::extensions::TPCTimeErrEncoding tEnc;
143-
tEnc.encoding.timeErr = trackExtra.trackTimeRes();
144-
const float dFwd = tEnc.getDeltaTFwd();
145-
const float dBwd = tEnc.getDeltaTBwd();
146-
// Equal, small forward/backward margins mean the track is bounded on both ends,
147-
// i.e. it crosses the CE: it cannot be moved and is already corrected elsewhere.
148-
const bool crossesCE = (dFwd == dBwd) && (dFwd < mMaxCECrossingDeltaTNS);
149-
if (!crossesCE) {
150-
const bool zPositive = track.getZ() > 0.f;
151-
const bool tglPositive = track.getTgl() > 0.f;
152-
int side = 0; // +1 = A, -1 = C, 0 = undetermined -> leave uncorrected
153-
if (zPositive == tglPositive) {
154-
// Consistent sign: the track converges to Z=0 at the beamline by construction.
155-
side = tglPositive ? 1 : -1;
156-
} else if (dBwd == 0.f && dFwd > 0.f) {
157-
// Bounded backward at the CE with room forward: no clusters on the opposite
158-
// side, so trust the measured Z rather than the (here inverted) tgl.
159-
side = zPositive ? 1 : -1;
160-
} else if (dBwd > 0.f) {
161-
// Large tgl track bounded at the readout side instead: trust tgl.
162-
side = tglPositive ? 1 : -1;
163-
}
164-
// else: degenerate case, track touches both CE and readout -> cannot be deduced/moved.
165-
// (in practice unreachable here: dFwd==dBwd==0 would already satisfy crossesCE above,
166-
// since dFwd/dBwd are always >= 0; kept explicit to mirror the reference logic 1:1.)
167-
168-
if (side > 0) {
169-
zShift = dDrift;
170-
} else if (side < 0) {
171-
zShift = -dDrift;
146+
if (!TPCVDriftManagerParam::Instance().useSideBasedCorrection) {
147+
// Legacy behaviour (default): infer the side from tgl alone.
148+
zShift = (track.getTgl() < 0.f) ? -dDrift : dDrift;
149+
} else {
150+
const auto sides = (trackExtra.flags() & (o2::aod::track::TrackFlags::TPCSideA | o2::aod::track::TrackFlags::TPCSideC));
151+
if (sides == o2::aod::track::TrackFlags::TPCSideA) {
152+
zShift = dDrift;
153+
} else if (sides == o2::aod::track::TrackFlags::TPCSideC) {
154+
zShift = -dDrift;
155+
} else if (sides == 0) {
156+
// Fallback for datasets produced before the TPC side flags were introduced (Feb. 2026).
157+
o2::aod::track::extensions::TPCTimeErrEncoding tEnc;
158+
tEnc.encoding.timeErr = trackExtra.trackTimeRes();
159+
const float dFwd = tEnc.getDeltaTFwd();
160+
const float dBwd = tEnc.getDeltaTBwd();
161+
// Equal, small forward/backward margins mean the track is bounded on both ends,
162+
// i.e. it crosses the CE: it cannot be moved and is already corrected elsewhere.
163+
const bool crossesCE = (dFwd == dBwd) && (dFwd < mMaxCECrossingDeltaTNS);
164+
if (!crossesCE) {
165+
const bool zPositive = track.getZ() > 0.f;
166+
const bool tglPositive = track.getTgl() > 0.f;
167+
int side = 0; // +1 = A, -1 = C, 0 = undetermined -> leave uncorrected
168+
if (zPositive == tglPositive) {
169+
// Consistent sign: the track converges to Z=0 at the beamline by construction.
170+
side = tglPositive ? 1 : -1;
171+
} else if (dBwd == 0.f && dFwd > 0.f) {
172+
// Bounded backward at the CE with room forward: no clusters on the opposite
173+
// side, so trust the measured Z rather than the (here inverted) tgl.
174+
side = zPositive ? 1 : -1;
175+
} else if (dBwd > 0.f) {
176+
// Large tgl track bounded at the readout side instead: trust tgl.
177+
side = tglPositive ? 1 : -1;
178+
}
179+
// else: degenerate case, track touches both CE and readout -> cannot be deduced/moved.
180+
// (in practice unreachable here: dFwd==dBwd==0 would already satisfy crossesCE above,
181+
// since dFwd/dBwd are always >= 0; kept explicit to mirror the reference logic 1:1.)
182+
183+
if (side > 0) {
184+
zShift = dDrift;
185+
} else if (side < 0) {
186+
zShift = -dDrift;
187+
}
172188
}
173189
}
190+
// else: track has clusters on both sides (crossed the CE) and is already corrected elsewhere
174191
}
175-
// else: track has clusters on both sides (crossed the CE) and is already corrected elsewhere
176192
track.setZ(track.getZ() + zShift);
177193
if constexpr (std::is_base_of_v<o2::track::TrackParCov, Track>) {
178194
track.setCov(track.getSigmaZ2() + dDriftErr * dDriftErr, o2::track::kSigZ2);

0 commit comments

Comments
 (0)