From c784b9ee43e30e1c8145c38ff1583241def89f1f Mon Sep 17 00:00:00 2001 From: Matthias Kleiner Date: Thu, 24 Sep 2026 18:00:34 +0200 Subject: [PATCH] TPC SCD: add clampTgSlp to keep residuals beyond MaxTgSlp The |tan(phi)| < param::MaxTgSlp condition on a TPC cluster residual is evaluated on the REFERENCE track's direction. For ITS-TPC tracks that direction carries the ITS curvature error, so dropping the cluster selects on that error (over-curved references reach the limit first), a one-sided selection that grows with radius for pt below ~0.3 GeV. With clampTgSlp=true the cluster is kept and tgSlp is stored saturated at +-MaxTgSlp (tgSlp == +-0x7fff, UnbinnedResid::isTgSlpClamped()); dy, dz, y, z are unchanged. Default false = unchanged behaviour and file content; the format is not changed. With clampTgSlp the saturated tgSlp is not the track's angle, and the voxel fit extracts dX from dY vs tan(phi) (also used by the map-correction check in staticMapCreator.C). ResidualsContainer::fill and staticMapCreator.C therefore skip UnbinnedResid::isTgSlpClamped() residuals when binning; they stay in the unbinned output. The vDrift estimate (dz vs z only) still uses them. No effect unless clampTgSlp is on. Co-Authored-By: Claude Sonnet 5 Co-Authored-By: Claude Opus 5.5 --- .../SpacePoints/SpacePointsCalibConfParam.h | 1 + .../include/SpacePoints/TrackInterpolation.h | 2 ++ .../SpacePoints/macro/staticMapCreator.C | 5 +++++ .../SpacePoints/src/ResidualAggregator.cxx | 5 +++++ .../SpacePoints/src/TrackInterpolation.cxx | 14 ++++++++++---- 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/SpacePointsCalibConfParam.h b/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/SpacePointsCalibConfParam.h index 5e25b4717118c..6ef3839991d04 100644 --- a/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/SpacePointsCalibConfParam.h +++ b/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/SpacePointsCalibConfParam.h @@ -48,6 +48,7 @@ struct SpacePointsCalibConfParam : public o2::conf::ConfigurableParamHelper= param::MaxTgSlp saturated (tgSlp = +-0x7fff, see UnbinnedResid::isTgSlpClamped) instead of dropping them: the cut is on the reference track's direction, so dropping selects on the reference's error float maxStep{2.f}; ///< maximum step for propagation bool debugTRDTOF{false}; ///< if true, ITS-TPC-TRD-TOF tracks and their seeding ITS-TPC-TRD track will both be interpolated and their residuals stored diff --git a/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/TrackInterpolation.h b/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/TrackInterpolation.h index 99e85cf19101a..2a662591b0ac8 100644 --- a/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/TrackInterpolation.h +++ b/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/TrackInterpolation.h @@ -94,6 +94,8 @@ struct UnbinnedResid { short channel{-1}; ///< extra channel info (ITS chip ID, TRD chamber, TOF main pad within the sector) bool rejected{false}; ///< residual is flagged as rejected in the validateTrack + /// true if tgSlp was saturated at +-param::MaxTgSlp (scdcalib.clampTgSlp): unclamped values have |tgSlp| <= 0x7fff - 1 + bool isTgSlpClamped() const { return tgSlp == 0x7fff || tgSlp == -0x7fff; } bool isTPC() const { return row < constants::MAXGLOBALPADROW; } bool isTRD() const { return row >= 160 && row < 166; } bool isTOF() const { return row == 170; } diff --git a/Detectors/TPC/calibration/SpacePoints/macro/staticMapCreator.C b/Detectors/TPC/calibration/SpacePoints/macro/staticMapCreator.C index 9ac12884d2a3f..3bf01f21f1dce 100644 --- a/Detectors/TPC/calibration/SpacePoints/macro/staticMapCreator.C +++ b/Detectors/TPC/calibration/SpacePoints/macro/staticMapCreator.C @@ -320,6 +320,11 @@ void staticMapCreator(std::string fileInput = "files.txt", if (useResidualsForVd && residualsVd.size() < 10'000'000UL) { residualsVd.push_back(residIn); } + if (residIn.isTgSlpClamped()) { + // scdcalib.clampTgSlp: tgSlp saturated -- the voxel fit (dX from dY vs tan(phi)) and the map correction below + // use it, so keep this residual out of the binned residuals + continue; + } int sec = residIn.sec; auto& residVecOut = binnedResidualsSec[sec]; auto& statVecOut = voxStatsSec[sec]; diff --git a/Detectors/TPC/calibration/SpacePoints/src/ResidualAggregator.cxx b/Detectors/TPC/calibration/SpacePoints/src/ResidualAggregator.cxx index b916e14dbf741..c5594ddc40b02 100644 --- a/Detectors/TPC/calibration/SpacePoints/src/ResidualAggregator.cxx +++ b/Detectors/TPC/calibration/SpacePoints/src/ResidualAggregator.cxx @@ -197,6 +197,11 @@ void ResidualsContainer::fill(const o2::dataformats::TFIDInfo& ti, const gsl::sp if (!writeBinnedResid) { continue; } + if (residIn.isTgSlpClamped()) { + // scdcalib.clampTgSlp: kept in the unbinned output, but its tgSlp is saturated and the voxel fit uses tgSlp (dX from + // dY vs tan(phi)), so it must not enter the binned residuals + continue; + } int sec = residIn.sec; auto& residVecOut = residuals[sec]; auto& statVecOut = stats[sec]; diff --git a/Detectors/TPC/calibration/SpacePoints/src/TrackInterpolation.cxx b/Detectors/TPC/calibration/SpacePoints/src/TrackInterpolation.cxx index 571bc00a48763..8265bce0582bd 100644 --- a/Detectors/TPC/calibration/SpacePoints/src/TrackInterpolation.cxx +++ b/Detectors/TPC/calibration/SpacePoints/src/TrackInterpolation.cxx @@ -725,8 +725,11 @@ void TrackInterpolation::interpolateTrack(int iSeed) const auto z = clusterResiduals[iCl].z; const auto sec = clusterResiduals[iCl].sec; const short flags = clusterResiduals[iCl].flags; - if ((std::abs(dy) < param::MaxResid) && (std::abs(dz) < param::MaxResid) && (std::abs(y) < param::MaxY) && (std::abs(z) < param::MaxZ) && (std::abs(tgPhi) < param::MaxTgSlp)) { - mClRes.emplace_back(dy, dz, tgPhi, y, z, iRow, sec, flags, rej); + // scdcalib.clampTgSlp: keep a cluster whose |tan(phi)| exceeds the packing range, with tgSlp saturated, instead of dropping it + const bool tgPhiOK = (std::abs(tgPhi) < param::MaxTgSlp) || mParams->clampTgSlp; + const float tgPhiStore = std::clamp(tgPhi, -param::MaxTgSlp, param::MaxTgSlp); + if ((std::abs(dy) < param::MaxResid) && (std::abs(dz) < param::MaxResid) && (std::abs(y) < param::MaxY) && (std::abs(z) < param::MaxZ) && tgPhiOK) { + mClRes.emplace_back(dy, dz, tgPhiStore, y, z, iRow, sec, flags, rej); mDetInfoRes.emplace_back().setTPC(mCacheDEDX[iRow].first, mCacheDEDX[iRow].second); // qtot, qmax ++nClValidated; } else { @@ -1076,8 +1079,11 @@ void TrackInterpolation::extrapolateTrack(int iSeed) const auto y = clusterResiduals[iCl].y; const auto z = clusterResiduals[iCl].z; const short flags = clusterResiduals[iCl].flags; - if ((std::abs(dy) < param::MaxResid) && (std::abs(dz) < param::MaxResid) && (std::abs(y) < param::MaxY) && (std::abs(z) < param::MaxZ) && (std::abs(tgPhi) < param::MaxTgSlp)) { - mClRes.emplace_back(dy, dz, tgPhi, y, z, iRow, clusterResiduals[iCl].sec, flags, rej); + // scdcalib.clampTgSlp: keep a cluster whose |tan(phi)| exceeds the packing range, with tgSlp saturated, instead of dropping it + const bool tgPhiOK = (std::abs(tgPhi) < param::MaxTgSlp) || mParams->clampTgSlp; + const float tgPhiStore = std::clamp(tgPhi, -param::MaxTgSlp, param::MaxTgSlp); + if ((std::abs(dy) < param::MaxResid) && (std::abs(dz) < param::MaxResid) && (std::abs(y) < param::MaxY) && (std::abs(z) < param::MaxZ) && tgPhiOK) { + mClRes.emplace_back(dy, dz, tgPhiStore, y, z, iRow, clusterResiduals[iCl].sec, flags, rej); mDetInfoRes.emplace_back().setTPC(mCacheDEDX[iRow].first, mCacheDEDX[iRow].second); // qtot, qmax ++nClValidated; } else {