Project
Loading...
Searching...
No Matches
CalculatedEdx.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
16
18#include "TPCBase/PadPos.h"
19#include "TPCBase/ROC.h"
20#include "TPCBase/Mapper.h"
28#include "TTree.h"
31#include "GPUO2InterfaceUtils.h"
33
34#include <algorithm>
35#include <cstdlib>
36#include <cstring>
37#include <limits>
38
39using namespace o2::tpc;
40
41namespace
42{
43// sentinel for minChargeTotROC/minChargeMaxROC
44constexpr float kNoValidCharge = std::numeric_limits<float>::max();
45
46// dEdxSettings::sameRowClusterMethod == 1: one synthesized cluster summing a mergeable group's fragments'
47// qTot/qMax and charge-weighted pad/time
48o2::tpc::ClusterNative buildMergedClusterSum(const std::vector<o2::tpc::ClusterNative>& fragments)
49{
50 float weightedPadSum = 0.f;
51 float weightedTimeSum = 0.f;
52 float totalCharge = 0.f;
53 uint16_t maxCharge = 0;
54
55 const o2::tpc::ClusterNative& firstCluster = fragments[0];
56 o2::tpc::ClusterNative combinedCluster = firstCluster;
57
58 for (const auto& cl : fragments) {
59 const float clPad = cl.getPad();
60 const float clTime = cl.getTime();
61 const uint16_t clqTot = cl.getQtot();
62 const uint16_t clqMax = cl.qMax;
63
64 weightedPadSum += clPad * clqTot;
65 weightedTimeSum += clTime * clqTot;
66 totalCharge += clqTot;
67 maxCharge = std::max(maxCharge, clqMax);
68 }
69
70 if (totalCharge > o2::tpc::ClusterNative::maxRegularQtot) {
71 combinedCluster.setSaturatedQtot(static_cast<uint32_t>(totalCharge));
72 } else {
73 combinedCluster.qTotPacked = static_cast<uint16_t>(totalCharge);
74 }
75 combinedCluster.qMax = maxCharge;
76 combinedCluster.padPacked = static_cast<uint16_t>(weightedPadSum / totalCharge * o2::tpc::ClusterNative::scalePadPacked);
77 combinedCluster.timeFlagsPacked = (static_cast<uint32_t>(weightedTimeSum / totalCharge * o2::tpc::ClusterNative::scaleTimePacked) & 0xFFFFFF) | (firstCluster.timeFlagsPacked & 0xFF000000);
78 return combinedCluster;
79}
80} // namespace
81
82const CalculatedEdx::RowFragment& CalculatedEdx::pickDominantFragment(const std::vector<RowFragment>& fragments)
83{
84 const RowFragment* dominant = &fragments[0];
85 for (const auto& frag : fragments) {
86 if (frag.cl.getQtot() > dominant->cl.getQtot()) {
87 dominant = &frag;
88 }
89 }
90 return *dominant;
91}
92
94{
95 mTPCCorrMapFull = TPCFastTransformHelperO2::instance()->create(0);
96 rebuildTPCCorrMapPOD();
97}
98
99void CalculatedEdx::rebuildTPCCorrMapPOD()
100{
101 // re-flatten the regular mTPCCorrMapFull into the POD buffer setRefit()'s GPU refitter reads
103 gpu::TPCFastTransformPOD::create(buffer, *mTPCCorrMapFull);
104 mTPCCorrMapBuffer = std::move(buffer);
105 mTPCCorrMap = mTPCCorrMapBuffer.get();
106}
107
108void CalculatedEdx::setTPCCorrMap(const o2::gpu::TPCFastTransform& corrMap, const o2::gpu::TPCFastTransform* corrMapRef, float lumiScale)
109{
110 if (mRefit) {
111 // the existing refitter holds a pointer into the correction map buffer we are about to replace; drop it
112 // rather than leave it dangling. Normal per-event usage (loadCalibsFromCCDB()/loadCalibsFromLocalCCDBFolder()
113 // followed by setRefit()) re-creates it right after this call with the new map, so this is not an error.
114 LOGP(warning, "CalculatedEdx::setTPCCorrMap() called after setRefit(); invalidating the existing refitter, call setRefit() again before using it.");
115 mRefit.reset();
116 }
117 mRefitExt.reset();
118 mTPCCorrMapFull = TPCFastTransformHelperO2::instance()->create(0, corrMap.getCorrection());
119 if (corrMapRef && lumiScale >= 0) {
120 // linear lumi scaling as in the reconstruction (TPCScalerSpec): (mean - ref) * lumiScale + ref
121 auto& correction = mTPCCorrMapFull->getCorrection();
122 TPCFastSpaceChargeCorrectionHelper::instance()->addCorrections(correction, 1., {{&corrMapRef->getCorrection(), -1.}});
123 TPCFastSpaceChargeCorrectionHelper::instance()->addCorrections(correction, lumiScale, {{&corrMapRef->getCorrection(), 1.}});
124 }
125 rebuildTPCCorrMapPOD();
126}
127
129{
130 if (mRefit) {
131 // see setTPCCorrMap() above: drop the now-stale refitter instead of leaving it dangling
132 LOGP(warning, "CalculatedEdx::setTPCVDrift() called after setRefit(); invalidating the existing refitter, call setRefit() again before using it.");
133 mRefit.reset();
134 }
135 mRefitExt.reset();
136 TPCFastTransformHelperO2::instance()->updateCalibration(*mTPCCorrMapFull, 0, v.corrFact, v.refVDrift, v.getTimeOffset());
137 rebuildTPCCorrMapPOD();
138}
139
140void CalculatedEdx::setMembers(std::vector<o2::tpc::TPCClRefElem>* tpcTrackClIdxVecInput, const o2::tpc::ClusterNativeAccess& clIndex, std::vector<o2::tpc::TrackTPC>* vTPCTracksArrayInp)
141{
142 mTracks = vTPCTracksArrayInp;
143 mTPCTrackClIdxVecInput = tpcTrackClIdxVecInput;
144 mClusterIndex = &clIndex;
145}
146
147void CalculatedEdx::setRefit(const unsigned int nHbfPerTf)
148{
149 mUseRefit = true;
150 mNHbfPerTf = nHbfPerTf;
151 mWarnedMissingRefit = false;
152 mRefitExt.reset();
153 auto sizeOcc = o2::gpu::GPUO2InterfaceRefit::fillOccupancyMapGetSize(nHbfPerTf, nullptr);
154 mTPCRefitterOccMap.resize(sizeOcc);
155 std::fill(mTPCRefitterOccMap.begin(), mTPCRefitterOccMap.end(), 0);
156 if (!mClusterIndex || !mTracks || !mTPCTrackClIdxVecInput) {
157 mRefit.reset();
158 mTPCRefitterShMap.clear();
159 return;
160 }
161 mTPCRefitterShMap.resize(mClusterIndex->nClustersTotal);
162 o2::gpu::GPUO2InterfaceRefit::fillSharedClustersAndOccupancyMap(mClusterIndex, *mTracks, mTPCTrackClIdxVecInput->data(), mTPCRefitterShMap.data(), mTPCRefitterOccMap.data(), nHbfPerTf);
163 mRefit = std::make_unique<o2::gpu::GPUO2InterfaceRefit>(mClusterIndex, mTPCCorrMap, mFieldNominalGPUBz, mTPCTrackClIdxVecInput->data(), nHbfPerTf, mTPCRefitterShMap.data(), mTPCRefitterOccMap.data(), mTPCRefitterOccMap.size());
164}
165
166void CalculatedEdx::fillMissingClusters(int missingClusters[4], const float minChargeTot[4], const float minChargeMax[4], int method, std::array<std::vector<float>, 5>& chargeTotROC, std::array<std::vector<float>, 5>& chargeMaxROC)
167{
168 if (method != 0 && method != 1) {
169 LOGP(info, "Unrecognized subthreshold cluster treatment. Not adding virtual charges to the track!");
170 return;
171 }
172
173 for (int roc = 0; roc < 4; roc++) {
174 // minChargeTot/MaxROC[roc] is only ever updated from an accepted real cluster in that ROC
175 // a region this track never accepted a single cluster in leaves it at kNoValidCharge, so skip
176 // this ROC's fill entirely rather than injecting the sentinel as if it were a measured charge
177 if (minChargeTot[roc] >= kNoValidCharge || minChargeMax[roc] >= kNoValidCharge) {
178 continue;
179 }
180 const float chargeTot = (method == 1) ? minChargeTot[roc] / 2.f : minChargeTot[roc];
181 const float chargeMax = (method == 1) ? minChargeMax[roc] / 2.f : minChargeMax[roc];
182 for (int i = 0; i < missingClusters[roc]; i++) {
183
184 chargeTotROC[roc].emplace_back(chargeTot);
185 chargeTotROC[4].emplace_back(chargeTot);
186
187 chargeMaxROC[roc].emplace_back(chargeMax);
188 chargeMaxROC[4].emplace_back(chargeMax);
189 }
190 }
191}
192
193void CalculatedEdx::handleSameRowClusters(o2::tpc::TrackTPC& track, std::vector<std::pair<unsigned char, unsigned char>>& rowOrder, std::map<std::pair<unsigned char, unsigned char>, std::vector<int>>& clustersByRow, std::set<std::pair<unsigned char, unsigned char>>& mergeableRows, std::map<int, std::tuple<unsigned char, unsigned char, unsigned int>>& clusterReferencesByIndex)
194{
195 // get number of clusters
196 const int nClusters = track.getNClusterReferences();
197
198 // group clusters by (sector, row)
199 for (int iCl = 0; iCl < nClusters; iCl++) {
200 unsigned char sectorIndex = 0;
201 unsigned char rowIndex = 0;
202 unsigned int clusterIndexNumb = 0;
203
204 track.getClusterReference(*mTPCTrackClIdxVecInput, iCl, sectorIndex, rowIndex, clusterIndexNumb);
205
206 const auto rowKey = std::make_pair(sectorIndex, rowIndex);
207 if (clustersByRow.find(rowKey) == clustersByRow.end()) {
208 rowOrder.emplace_back(rowKey);
209 }
210
211 // add the cluster index to the corresponding (sector, row) key in clustersByRow
212 clustersByRow[rowKey].emplace_back(iCl);
213
214 // store the reference data in clusterReferencesByIndex
215 clusterReferencesByIndex[iCl] = std::make_tuple(sectorIndex, rowIndex, clusterIndexNumb);
216 }
217
218 // flag groups of several clusters that ended up in the same (sector, row) as eligible for
219 // dEdxSettings::sameRowClusterMethod to merge but ONLY when they are close in pad and time
220 for (const auto& [rowKey, clusterIndices] : clustersByRow) {
221 if (clusterIndices.size() <= 1) {
222 continue;
223 }
224
225 // proximity gate: skip the merge if any pair in the group is farther apart than the pad/time window
226 float minPad = 1e9f, maxPad = -1e9f, minTime = 1e9f, maxTime = -1e9f;
227 for (int clusterIdx : clusterIndices) {
228 const o2::tpc::ClusterNative& cl = track.getCluster(*mTPCTrackClIdxVecInput, clusterIdx, *mClusterIndex);
229 minPad = std::min(minPad, cl.getPad());
230 maxPad = std::max(maxPad, cl.getPad());
231 minTime = std::min(minTime, cl.getTime());
232 maxTime = std::max(maxTime, cl.getTime());
233 }
234 if ((maxPad - minPad) > mSameRowMaxPadDiff || (maxTime - minTime) > mSameRowMaxTimeDiff) {
235 continue; // looper legs / distinct crossings -> keep as separate samples
236 }
237
238 mergeableRows.insert(rowKey);
239 }
240}
241
242void CalculatedEdx::handleSameRowClusters(const std::vector<o2::tpc::ClusterNative>& clusters, const ClInfoVec& clusterInfos, std::vector<std::pair<unsigned char, unsigned char>>& rowOrder, std::map<std::pair<unsigned char, unsigned char>, std::vector<int>>& clustersByRow, std::set<std::pair<unsigned char, unsigned char>>& mergeableRows)
243{
244 const int nClusters = static_cast<int>(clusters.size());
245
246 // group clusters by (sector, row)
247 for (int iCl = 0; iCl < nClusters; iCl++) {
248 const auto rowKey = std::make_pair(clusterInfos[iCl].sectorIndex, clusterInfos[iCl].rowIndex);
249 if (clustersByRow.find(rowKey) == clustersByRow.end()) {
250 rowOrder.emplace_back(rowKey);
251 }
252
253 // add the cluster index to the corresponding (sector, row) key in clustersByRow
254 clustersByRow[rowKey].emplace_back(iCl);
255 }
256
257 // flag groups of several clusters in the same (sector, row) as eligible for dEdxSettings::sameRowClusterMethod to merge, only when they are close in pad and time
258 for (const auto& [rowKey, clusterIndices] : clustersByRow) {
259 if (clusterIndices.size() <= 1) {
260 continue;
261 }
262
263 // proximity gate: skip the merge if any pair in the group is farther apart than the pad/time window
264 float minPad = 1e9f, maxPad = -1e9f, minTime = 1e9f, maxTime = -1e9f;
265 for (int clusterIdx : clusterIndices) {
266 const o2::tpc::ClusterNative& cl = clusters[clusterIdx];
267 minPad = std::min(minPad, cl.getPad());
268 maxPad = std::max(maxPad, cl.getPad());
269 minTime = std::min(minTime, cl.getTime());
270 maxTime = std::max(maxTime, cl.getTime());
271 }
272 if ((maxPad - minPad) > mSameRowMaxPadDiff || (maxTime - minTime) > mSameRowMaxTimeDiff) {
273 continue; // looper legs / distinct crossings -> keep as separate samples
274 }
275
276 mergeableRows.insert(rowKey);
277 }
278}
279
280void CalculatedEdx::gatherRowClusterData(o2::tpc::TrackTPC& track, std::vector<RowClusterData>& rowData, AverageOccupancy& averageOcc)
281{
282 rowData.clear();
283 averageOcc = AverageOccupancy{};
284 if (mDebug) {
285 ++mDebugTrackIndex;
286 }
287
288 const bool refitFailed = refitTrack(track);
289
290 // handle same (sector, row) clusters
291 std::vector<std::pair<unsigned char, unsigned char>> rowOrder;
292 std::map<std::pair<unsigned char, unsigned char>, std::vector<int>> clustersByRow;
293 std::set<std::pair<unsigned char, unsigned char>> mergeableRows;
294 std::map<int, std::tuple<unsigned char, unsigned char, unsigned int>> clusterReferencesByIndex;
295
296 handleSameRowClusters(track, rowOrder, clustersByRow, mergeableRows, clusterReferencesByIndex);
297
298 rowData.reserve(rowOrder.size());
299
300 // per-region occupancy, for the average occupancy output
301 std::array<std::vector<unsigned int>, 4> occupancyROC;
302
303 // for tracking missing clusters
304 unsigned char rowIndexOld = 255;
305 unsigned char sectorIndexOld = 255;
306
307 // loop over the clusters in the track's row-traversal order (rowOrder)
308 for (const auto& rowKey : rowOrder) {
309 const auto& clusterIndices = clustersByRow.at(rowKey);
310 const unsigned char rowIndex = rowKey.second;
311 const unsigned char sectorIndex = rowKey.first;
312
313 std::vector<o2::tpc::ClusterNative> fragmentClusters;
314 std::vector<bool> fragmentIsShared;
315 fragmentClusters.reserve(clusterIndices.size());
316 fragmentIsShared.reserve(clusterIndices.size());
317 for (const int clusterIdx : clusterIndices) {
318 fragmentClusters.emplace_back(track.getCluster(*mTPCTrackClIdxVecInput, clusterIdx, *mClusterIndex));
319 const auto& [fragSectorIndex, fragRowIndex, clusterIndexNumb] = clusterReferencesByIndex[clusterIdx];
320 const unsigned int absoluteIndex = mClusterIndex->clusterOffset[fragSectorIndex][fragRowIndex] + clusterIndexNumb;
321 fragmentIsShared.emplace_back(mRefit ? (mTPCRefitterShMap[absoluteIndex] & o2::gpu::GPUTPCGMMergedTrackHit::flagShared) : 0);
322 }
323
324 const bool mergeable = mergeableRows.count(rowKey) > 0;
325 gatherRowClusterDataForRow(track, fragmentClusters, fragmentIsShared, sectorIndex, rowIndex, mergeable, rowIndexOld, sectorIndexOld, occupancyROC, rowData, refitFailed);
326 rowData.back().inputClusterIndices = clusterIndices;
327 rowIndexOld = rowIndex;
328 sectorIndexOld = sectorIndex;
329 }
330
331 // calculate average cl occupancy for the track per TPC region; skip clusters where getOccupancy() had no data (sentinel -1)
332 double* const averageOccROC[4] = {&averageOcc.IROC, &averageOcc.OROC1, &averageOcc.OROC2, &averageOcc.OROC3};
333 for (int roc = 0; roc < 4; roc++) {
334 unsigned int sumOcc = 0;
335 size_t nValidOcc = 0;
336 for (const unsigned int occ : occupancyROC[roc]) {
337 if (occ != static_cast<unsigned int>(-1)) {
338 sumOcc += occ;
339 ++nValidOcc;
340 }
341 }
342 if (nValidOcc > 0) {
343 *averageOccROC[roc] = static_cast<double>(sumOcc) / nValidOcc;
344 }
345 }
346}
347
348bool CalculatedEdx::refitTrack(o2::tpc::TrackTPC& track)
349{
350 if (!mUseRefit) {
351 return false;
352 }
353 if (!mRefit) {
354 if (!mWarnedMissingRefit) {
355 LOGP(error, "CalculatedEdx: setRefit() is set but there is no refitter for the track's cluster references (setMembers() missing, or the refitter was dropped by setTPCCorrMap()/setTPCVDrift()), propagating the original tracks instead");
356 mWarnedMissingRefit = true;
357 }
358 return true;
359 }
360 // RefitTrackAsGPU() refits the whole track from its clusters (not just up to the current row) and may write into the track even if it fails, so keep the original state to fall back from
361 const o2::track::TrackParCov trackBeforeRefit = track;
362 // outward refit: the track ends at the outermost cluster, i.e. at the start of the cluster reference list that is traversed row by row
363 const bool failed = (mRefit->RefitTrackAsGPU(track, true, true) < 0) || std::isnan(track.getParam(1));
364 if (failed) {
365 static_cast<o2::track::TrackParCov&>(track) = trackBeforeRefit;
366 }
367 return failed;
368}
369
370bool CalculatedEdx::refitTrack(o2::tpc::TrackTPC& track, const std::vector<o2::tpc::ClusterNative>& clusters, const ClInfoVec& clusterInfos)
371{
372 if (!mUseRefit) {
373 return false;
374 }
375 const size_t nCl = clusters.size();
376 if (nCl == 0 || nCl != clusterInfos.size() || nCl > std::numeric_limits<uint16_t>::max()) {
377 return true;
378 }
379
380 if (!mExtClusterAccess) {
381 mExtClusterAccess = std::make_unique<o2::tpc::ClusterNativeAccess>();
382 }
383 auto& access = *mExtClusterAccess;
384 std::memset(access.nClusters, 0, sizeof(access.nClusters));
385 for (const auto& info : clusterInfos) {
386 if (info.sectorIndex >= constants::MAXSECTOR || info.rowIndex >= constants::MAXGLOBALPADROW) {
387 return true;
388 }
389 ++access.nClusters[info.sectorIndex][info.rowIndex];
390 }
391 const auto* refsBefore = mExtClusterRefs.data();
392 const auto* shMapBefore = mExtShMap.data();
393 mExtClusters.resize(nCl);
394 mExtClusterRefs.assign(nCl + (2 * nCl + sizeof(o2::tpc::TPCClRefElem) - 1) / sizeof(o2::tpc::TPCClRefElem), 0);
395 mExtShMap.assign(nCl, 0);
396 access.clustersLinear = mExtClusters.data();
397 access.clustersMCTruth = nullptr;
398 access.setOffsetPtrs();
399
400 std::map<std::pair<unsigned char, unsigned char>, unsigned int> nPlaced;
401 auto* sectorRowIndices = reinterpret_cast<uint8_t*>(mExtClusterRefs.data() + nCl);
402 for (size_t iCl = 0; iCl < nCl; ++iCl) {
403 const auto& info = clusterInfos[iCl];
404 const unsigned int indexInRow = nPlaced[{info.sectorIndex, info.rowIndex}]++;
405 const unsigned int linearIndex = access.clusterOffset[info.sectorIndex][info.rowIndex] + indexInRow;
406 mExtClusters[linearIndex] = clusters[iCl];
407 mExtShMap[linearIndex] = info.isShared ? o2::gpu::GPUTPCGMMergedTrackHit::flagShared : 0;
408 mExtClusterRefs[iCl] = indexInRow;
409 sectorRowIndices[iCl] = info.sectorIndex;
410 sectorRowIndices[iCl + nCl] = info.rowIndex;
411 }
412
413 // the refitter keeps raw pointers to the buffers, so recreate it when they moved
414 if (!mRefitExt || refsBefore != mExtClusterRefs.data() || shMapBefore != mExtShMap.data()) {
415 mRefitExt = std::make_unique<o2::gpu::GPUO2InterfaceRefit>(mExtClusterAccess.get(), mTPCCorrMap, mFieldNominalGPUBz, mExtClusterRefs.data(), mNHbfPerTf, mExtShMap.data(), mTPCRefitterOccMap.data(), mTPCRefitterOccMap.size());
416 }
417
418 const o2::track::TrackParCov trackBeforeRefit = track;
419 float chi2 = 0.f;
420 const o2::tpc::TrackTPCClusRef clusRef(0, static_cast<uint16_t>(nCl));
421 const bool failed = (mRefitExt->RefitTrackAsGPU(static_cast<o2::track::TrackParCov&>(track), clusRef, track.getTime0(), &chi2, true, true) < 0) || std::isnan(track.getParam(1));
422 if (failed) {
423 static_cast<o2::track::TrackParCov&>(track) = trackBeforeRefit;
424 } else {
425 track.setChi2(chi2);
426 }
427 return failed;
428}
429
430bool CalculatedEdx::propagateTrackToX(o2::track::TrackParCov& track, float xPosition, unsigned char sectorIndex) const
431{
432 const o2::track::TrackParCov trackBackup = track;
433 bool check = track.rotate(o2::math_utils::detail::sector2Angle<float>(sectorIndex));
434 if (check) {
435 check = o2::base::Propagator::Instance()->PropagateToXBxByBz(track, xPosition, 0.999f, 0.5f, o2::base::Propagator::MatCorrType::USEMatCorrLUT);
436 }
437 if (!check) {
438 track = trackBackup;
439 check = track.rotate(o2::math_utils::detail::sector2Angle<float>(sectorIndex));
440 if (check) {
441 check = o2::base::Propagator::Instance()->PropagateToXBxByBz(track, xPosition, 0.999f, 0.5f, o2::base::Propagator::MatCorrType::USEMatCorrNONE);
442 }
443 }
444 if (!check) {
445 track = trackBackup;
446 check = track.rotateParam(o2::math_utils::detail::sector2Angle<float>(sectorIndex));
447 if (check) {
448 check = track.propagateParamTo(xPosition, mFieldNominalGPUBz);
449 }
450 }
451 if (!check) {
452 track = trackBackup;
453 }
454 return check;
455}
456
457void CalculatedEdx::gatherRowClusterDataForRow(o2::tpc::TrackTPC& track, const std::vector<o2::tpc::ClusterNative>& fragmentClusters, const std::vector<bool>& fragmentIsShared, unsigned char sectorIndex, unsigned char rowIndex, bool mergeable, unsigned char rowIndexOld, unsigned char sectorIndexOld, std::array<std::vector<unsigned int>, 4>& occupancyROC, std::vector<RowClusterData>& rowData, bool refitFailed)
458{
459 RowClusterData row;
460 row.sectorIndex = sectorIndex;
461 row.rowIndex = rowIndex;
462 row.mergeable = mergeable;
463
464 // get region and stack
465 const int region = Mapper::REGION[rowIndex];
466 const CRU cru(Sector(sectorIndex), region);
467 const auto stack = cru.gemStack();
468 StackID stackID{sectorIndex, stack};
469 const int stackNumber = static_cast<int>(stack);
470
471 row.region = region;
472 row.stack = stack;
473 row.stackID = stackID;
474 row.stackNumber = stackNumber;
475
476 // per-fragment quantities: pad depends on the individual cluster's own pad, so threshold/gain/gainResidual/occupancy
477 // all looked up by pad, are computed once per fragment here
478 row.fragments.reserve(fragmentClusters.size());
479 for (size_t iFrag = 0; iFrag < fragmentClusters.size(); ++iFrag) {
480 const o2::tpc::ClusterNative& cl = fragmentClusters[iFrag];
481 RowFragment frag;
482 frag.cl = cl;
483 frag.isShared = fragmentIsShared[iFrag];
484 frag.pad = std::clamp(static_cast<unsigned int>(cl.getPad() + 0.5f), static_cast<unsigned int>(0), Mapper::PADSPERROW[region][Mapper::getLocalRowFromGlobalRow(rowIndex)] - 1); // the left side of the pad is defined at e.g. 3.5 and the right side at 4.5
485 frag.occupancy = getOccupancy(cl.getTime());
486 frag.threshold = mCalibCont.getZeroSupressionThreshold(sectorIndex, rowIndex, frag.pad);
487 frag.gain = mCalibCont.getGain(sectorIndex, rowIndex, frag.pad);
488 frag.gainResidual = mCalibCont.getResidualGain(sectorIndex, rowIndex, frag.pad);
489
490 row.fragments.emplace_back(std::move(frag));
491 }
492
493 // dEdxSettings::sameRowClusterMethod==1's merged sample
494 if (mergeable && fragmentClusters.size() > 1) {
495 row.mergedFragment.cl = buildMergedClusterSum(fragmentClusters);
496 row.mergedFragment.pad = std::clamp(static_cast<unsigned int>(row.mergedFragment.cl.getPad() + 0.5f), static_cast<unsigned int>(0), Mapper::PADSPERROW[region][Mapper::getLocalRowFromGlobalRow(rowIndex)] - 1);
497 row.mergedFragment.occupancy = getOccupancy(row.mergedFragment.cl.getTime());
498 row.mergedFragment.threshold = mCalibCont.getZeroSupressionThreshold(sectorIndex, rowIndex, row.mergedFragment.pad);
499 row.mergedFragment.gain = mCalibCont.getGain(sectorIndex, rowIndex, row.mergedFragment.pad);
500 row.mergedFragment.gainResidual = mCalibCont.getResidualGain(sectorIndex, rowIndex, row.mergedFragment.pad);
501 row.mergedFragment.isShared = row.fragments[0].isShared; // preserves the historical "first fragment's isShared" choice for a merged sample
502 }
503
504 // occupancy sample(s) for this row's average occupancy contribution
505 std::array<std::vector<unsigned int>, 4>::size_type occStackIdx;
506 if (stack == GEMstack::IROCgem) {
507 occStackIdx = 0;
508 } else if (stack == GEMstack::OROC1gem) {
509 occStackIdx = 1;
510 } else if (stack == GEMstack::OROC2gem) {
511 occStackIdx = 2;
512 } else {
513 occStackIdx = 3;
514 }
515 if (mergeable && fragmentClusters.size() > 1) {
516 occupancyROC[occStackIdx].emplace_back(row.mergedFragment.occupancy);
517 } else {
518 for (const auto& frag : row.fragments) {
519 occupancyROC[occStackIdx].emplace_back(frag.occupancy);
520 }
521 }
522
523 // get the x position of the track: the corrected cluster x (charge-weighted for several clusters in the row
524 float xPosition = Mapper::instance().getPadCentre(PadPos(rowIndex, 0)).X();
525 if (mPropagateToClusterX) {
526 float sumX = 0.f, sumQ = 0.f;
527 for (const auto& cl : fragmentClusters) {
528 float x, y, z;
529 mTPCCorrMap->Transform(sectorIndex, rowIndex, cl.getPad(), cl.getTime(), x, y, z, track.getTime0());
530 sumX += x * cl.getQtot();
531 sumQ += cl.getQtot();
532 }
533 if (sumQ > 0.f) {
534 xPosition = sumX / sumQ;
535 }
536 }
537 bool check = true;
538 if (mUseRefit || mPropagateTrack || !mPropagateParams) {
539 // mUseRefit: the track was refit once in gatherRowClusterData() and is only propagated from row to row here, if that refit failed, the propagation starts from the track's original state
540 // otherwise: propagate this track to the plane X=xk (cm) in the field "b" (kG)
541 check = propagateTrackToX(track, xPosition, sectorIndex);
542 } else {
543 // propagate the params of the track instead of full propagation; same rollback rationale as mPropagateTrack above
544 const o2::track::TrackParCov trackBackup = track;
545 check = track.rotateParam(o2::math_utils::detail::sector2Angle<float>(sectorIndex));
546 if (check) {
547 check = track.propagateParamTo(xPosition, mFieldNominalGPUBz);
548 }
549 if (!check) {
550 static_cast<o2::track::TrackParCov&>(track) = trackBackup;
551 }
552 }
553
554 row.propagationFailed = (!check || std::isnan(track.getParam(1)));
555 row.refitFellBack = mUseRefit && refitFailed && !row.propagationFailed;
556 ++mNRowsProcessed;
557 if (row.propagationFailed) {
558 ++mNPropagationFailed;
559 } else if (row.refitFellBack) {
560 ++mNRefitFallback;
561 }
562
563 // snapshot of the track state after refit/propagation to this row; reused by calculatedEdxFromRowData() for every settings entry
564 row.trackSnapshot = track;
565
566 // number of rows skipped between this row and the previous entry in rowOrder
567 row.sameSectorAsPrevRow = (sectorIndexOld == sectorIndex);
568 row.missingClusters = (rowIndexOld == 255) ? 0 : (std::abs(static_cast<int>(rowIndex) - static_cast<int>(rowIndexOld)) - 1);
569
570 // veto the gap as a subthreshold candidate if any of its missing row(s) would land on a dead channel or off the padrow edge
571 row.missingClusterGapDeadOrEdge = false;
572 if (row.missingClusters > 0 && row.missingClusters <= mMaxMissingCl) {
573 const o2::gpu::GPUTPCGeometry gpuGeom;
574 const RowClusterData& prevRow = rowData.back();
575 // bracket the gap by its lower/upper real row, independent of the rowOrder direction
576 const int rowLo = std::min<int>(rowIndex, rowIndexOld);
577 const int rowHi = std::max<int>(rowIndex, rowIndexOld);
578 // count every missing row in its own stack
579 for (int missingRow = rowLo + 1; missingRow < rowHi; ++missingRow) {
580 ++row.missingClustersStack[static_cast<int>(CRU(Sector(sectorIndex), Mapper::REGION[missingRow]).gemStack())];
581 }
582 // rotate the previous cluster into this row's sector frame
583 const float yPrevOwn = gpuGeom.LinearPad2Y(sectorIndexOld, rowIndexOld, prevRow.fragments[0].cl.getPad());
584 float yPrev = yPrevOwn;
585 if (sectorIndexOld != sectorIndex) {
586 if ((sectorIndexOld < Sector::MAXSECTOR / 2) != (sectorIndex < Sector::MAXSECTOR / 2)) {
587 row.missingClusterGapDeadOrEdge = true;
588 }
589 const float dAlpha = o2::math_utils::detail::sector2Angle<float>(sectorIndexOld) - o2::math_utils::detail::sector2Angle<float>(sectorIndex);
590 yPrev = gpuGeom.Row2X(rowIndexOld) * std::sin(dAlpha) + yPrevOwn * std::cos(dAlpha);
591 }
592 const float yCur = gpuGeom.LinearPad2Y(sectorIndex, rowIndex, row.fragments[0].cl.getPad());
593 const float yLo = (rowLo == static_cast<int>(rowIndexOld)) ? yPrev : yCur;
594 const float yHi = (rowHi == static_cast<int>(rowIndexOld)) ? yPrev : yCur;
595 for (int k = 1; k <= row.missingClusters && !row.missingClusterGapDeadOrEdge; ++k) {
596 const unsigned char missingRow = static_cast<unsigned char>(rowLo + k);
597 const float frac = static_cast<float>(k) / (row.missingClusters + 1);
598 const float missingPad = gpuGeom.LinearY2Pad(sectorIndex, missingRow, yLo + (yHi - yLo) * frac);
599 if (missingPad < 0.f || missingPad >= gpuGeom.NPads(missingRow)) {
600 row.missingClusterGapDeadOrEdge = true;
601 break;
602 }
603 const int missingRegion = Mapper::REGION[missingRow];
604 const unsigned char missingPadClamped = std::clamp(static_cast<unsigned int>(missingPad + 0.5f), static_cast<unsigned int>(0), Mapper::PADSPERROW[missingRegion][Mapper::getLocalRowFromGlobalRow(missingRow)] - 1);
605 if (mCalibCont.isDead(static_cast<unsigned int>(sectorIndex), static_cast<gpu::tpccf::Row>(missingRow), static_cast<gpu::tpccf::Pad>(missingPadClamped))) {
606 row.missingClusterGapDeadOrEdge = true;
607 break;
608 }
609 }
610 }
611
612 rowData.emplace_back(std::move(row));
613}
614
615void CalculatedEdx::gatherRowClusterData(o2::tpc::TrackTPC& track, const std::vector<o2::tpc::ClusterNative>& clusters, const ClInfoVec& clusterInfos, std::vector<RowClusterData>& rowData, AverageOccupancy& averageOcc)
616{
617 rowData.clear();
618 averageOcc = AverageOccupancy{};
619 if (mDebug) {
620 ++mDebugTrackIndex;
621 }
622
623 const bool refitFailed = refitTrack(track, clusters, clusterInfos);
624
625 // handle same (sector, row) clusters
626 std::vector<std::pair<unsigned char, unsigned char>> rowOrder;
627 std::map<std::pair<unsigned char, unsigned char>, std::vector<int>> clustersByRow;
628 std::set<std::pair<unsigned char, unsigned char>> mergeableRows;
629
630 handleSameRowClusters(clusters, clusterInfos, rowOrder, clustersByRow, mergeableRows);
631
632 rowData.reserve(rowOrder.size());
633
634 // per-region occupancy, for the average occupancy output
635 std::array<std::vector<unsigned int>, 4> occupancyROC;
636
637 // for tracking missing clusters
638 unsigned char rowIndexOld = 255;
639 unsigned char sectorIndexOld = 255;
640
641 // loop over the clusters in the rowOrder; the refit/propagation to a row is done exactly once here regardless of how many raw clusters the row has
642 for (const auto& rowKey : rowOrder) {
643 const auto& clusterIndices = clustersByRow.at(rowKey);
644 const unsigned char rowIndex = rowKey.second;
645 const unsigned char sectorIndex = rowKey.first;
646
647 std::vector<o2::tpc::ClusterNative> fragmentClusters;
648 std::vector<bool> fragmentIsShared;
649 fragmentClusters.reserve(clusterIndices.size());
650 fragmentIsShared.reserve(clusterIndices.size());
651 for (const int clusterIdx : clusterIndices) {
652 fragmentClusters.emplace_back(clusters[clusterIdx]);
653 // isShared cannot be looked up from mTPCRefitterShMap for externally supplied clusters, so it is taken from the info directly
654 fragmentIsShared.emplace_back(clusterInfos[clusterIdx].isShared);
655 }
656
657 const bool mergeable = mergeableRows.count(rowKey) > 0;
658 gatherRowClusterDataForRow(track, fragmentClusters, fragmentIsShared, sectorIndex, rowIndex, mergeable, rowIndexOld, sectorIndexOld, occupancyROC, rowData, refitFailed);
659 rowData.back().inputClusterIndices = clusterIndices; // positions in the externally supplied clusters vector grouped into this row
660 rowIndexOld = rowIndex;
661 sectorIndexOld = sectorIndex;
662 }
663
664 // calculate average cl occupancy for the track per TPC region; skip clusters where getOccupancy() had no data (sentinel -1)
665 double* const averageOccROC[4] = {&averageOcc.IROC, &averageOcc.OROC1, &averageOcc.OROC2, &averageOcc.OROC3};
666 for (int roc = 0; roc < 4; roc++) {
667 unsigned int sumOcc = 0;
668 size_t nValidOcc = 0;
669 for (const unsigned int occ : occupancyROC[roc]) {
670 if (occ != static_cast<unsigned int>(-1)) {
671 sumOcc += occ;
672 ++nValidOcc;
673 }
674 }
675 if (nValidOcc > 0) {
676 *averageOccROC[roc] = static_cast<double>(sumOcc) / nValidOcc;
677 }
678 }
679}
680
681void CalculatedEdx::calculatedEdxFromRowData(const std::vector<RowClusterData>& rowData, const dEdxSettings& settings, size_t settingsIndex, float trackTime0, const o2::tpc::TrackTPC& trackOrig, const AverageOccupancy& averageOcc, dEdxInfo& output, const MCCompLabel* mcLabel)
682{
683 // NHits and NHitsSubthreshold values per region
684 int nClsROC[4] = {0, 0, 0, 0};
685 int nClsSubThreshROC[4] = {0, 0, 0, 0};
686
687 const unsigned short sameRowClusterMethod = (settings.sameRowClusterMethod <= 2) ? settings.sameRowClusterMethod : 0;
688 if (settings.sameRowClusterMethod > 2) {
689 LOGP(warning, "Unrecognized sameRowClusterMethod {} (expected 0, 1, or 2); treating same-row cluster groups as method 0 (do not merge)", settings.sameRowClusterMethod);
690 }
691
692 // corrected qTot and qMax values per region
693 const int nType = 5;
694 std::array<std::vector<float>, nType> chargeTotROC;
695 std::array<std::vector<float>, nType> chargeMaxROC;
696 for (int i = 0; i < nType; ++i) {
697 chargeTotROC[i].reserve(Mapper::PADROWS);
698 chargeMaxROC[i].reserve(Mapper::PADROWS);
699 }
700
701 // per-region (IROC, OROC1, OROC2, OROC3) running minimum charge among accepted clusters, used as the virtual charge for that region's subthreshold clusters below
702 float minChargeTotROC[4] = {kNoValidCharge, kNoValidCharge, kNoValidCharge, kNoValidCharge};
703 float minChargeMaxROC[4] = {kNoValidCharge, kNoValidCharge, kNoValidCharge, kNoValidCharge};
704
705 o2::utils::TreeStreamRedirector* debugStreamer = nullptr;
706 std::vector<unsigned int> occupancyVector;
707 if (mDebug) {
708 setStreamer(settings.debugRootFile.c_str());
709 debugStreamer = mStreamers.at(settings.debugRootFile).get();
710 occupancyVector.reserve(rowData.size());
711 }
712
713 // a gap is not filled as a subthreshold cluster when the row that closes it sits within the outermost min(nRows/2, mSubThreshEdgeRows) rows
714 const int edgeRowCut = std::min<int>(static_cast<int>(rowData.size()) / 2, mSubThreshEdgeRows);
715
716 for (size_t iRowData = 0; iRowData < rowData.size(); ++iRowData) {
717 const auto& row = rowData[iRowData];
718
719 // one effective sample per row for this settings entry, per dEdxSettings::sameRowClusterMethod; pointers into
720 // row.fragments/row.mergedFragment (both owned by rowData, alive for the whole calculatedEdxFromRowData() call)
721 std::vector<const RowFragment*> samples;
722
723 const bool doMerge = row.mergeable && row.fragments.size() > 1 && sameRowClusterMethod != 0;
724 if (!doMerge) {
725 samples.reserve(row.fragments.size());
726 for (const auto& frag : row.fragments) {
727 samples.push_back(&frag);
728 }
729 } else if (sameRowClusterMethod == 2) {
730 samples.push_back(&pickDominantFragment(row.fragments));
731 } else { // sameRowClusterMethod == 1: use the sum-merged sample gatherRowClusterDataForRow() already computed once for this row
732 samples.push_back(&row.mergedFragment);
733 }
734
735 // ExcludeSamePadRowCl: true whenever the row-group HAD >1 raw fragment, regardless of whether this settings entry actually merged them
736 const bool isCombined = row.fragments.size() > 1;
737
738 // find missing clusters
739 const int missingClusters = row.missingClusters;
740 const bool excludeSectorBoundary = ((settings.clusterMask & ClusterFlags::ExcludeSectorBoundaries) == ClusterFlags::ExcludeSectorBoundaries) && !row.sameSectorAsPrevRow;
741 if ((missingClusters > 0) && (missingClusters <= mMaxMissingCl) && !row.missingClusterGapDeadOrEdge && (static_cast<int>(iRowData) >= edgeRowCut) && !excludeSectorBoundary) {
742 for (int roc = 0; roc < 4; roc++) {
743 nClsSubThreshROC[roc] += row.missingClustersStack[roc];
744 nClsROC[roc] += row.missingClustersStack[roc];
745 }
746 }
747
748 for (const auto& sample : samples) {
749 if (mDebug) {
750 occupancyVector.emplace_back(sample->occupancy);
751 }
752
753 // get cluster values
754 float chargeTot = sample->cl.getQtot();
755 float chargeMax = sample->cl.getQmax();
756
757 // corrections
758 float effectiveLength = 1.0f;
759 float effectiveLengthTot = 1.0f;
760 float effectiveLengthMax = 1.0f;
761 float gain = 1.0f;
762 float gainResidual = 1.0f;
763 float corrTot = 1.0f;
764 float corrMax = 1.0f;
765 float scCorr = 1.0f;
766
767 int excludeCl = 0; // works as a bit mask
768 const uint8_t flagsCl = sample->cl.getFlags();
770 excludeCl += 0b001; // 1 for single cluster
771 }
773 excludeCl += 0b010; // 2 for split pad cluster
774 }
776 excludeCl += 0b0100; // 4 for split time cluster
777 }
779 excludeCl += 0b01000; // 8 for split cluster
780 }
782 excludeCl += 0b010000; // 16 for edge cluster
783 }
784 if (((settings.clusterMask & ClusterFlags::ExcludeSharedCl) == ClusterFlags::ExcludeSharedCl) && sample->isShared) {
785 excludeCl += 0b0100000; // 32 for shared cluster
786 }
788 excludeCl += 0b01000000; // 64 for combined cluster
789 }
790 if ((settings.stackBoundaryMethod == 1 || settings.stackBoundaryMethod == 2) && isInStackBoundaries(row.stackNumber, row.rowIndex, settings.stackBoundaryMethod)) {
791 excludeCl += 0b010000000; // 128 for stack boundary cluster
792 }
793 if (row.propagationFailed) {
794 excludeCl += 0b01000000000; // 512 for failure of track propagation or refit
795 }
796
797 // get effective length
799 effectiveLength = getTrackTopologyCorrection(row.trackSnapshot, row.region);
800 chargeTot /= effectiveLength;
801 chargeMax /= effectiveLength;
802 };
803
804 const bool gainFullApplied = (settings.correctionMask & CorrectionFlags::GainFull) == CorrectionFlags::GainFull;
805 float topoChargeTot = chargeTot;
806 float topoChargeMax = chargeMax;
807 if (gainFullApplied) {
808 gain = sample->gain;
809 chargeTot /= gain;
810 chargeMax /= gain;
811 } else {
812 topoChargeTot *= sample->gain;
813 topoChargeMax *= sample->gain;
814 }
815
816 // topology correction
818 effectiveLengthTot = getTrackTopologyCorrectionPol(row.trackSnapshot, sample->cl, row.region, topoChargeTot, ChargeType::Tot, sample->threshold);
819 effectiveLengthMax = getTrackTopologyCorrectionPol(row.trackSnapshot, sample->cl, row.region, topoChargeMax, ChargeType::Max, sample->threshold);
820 chargeTot /= effectiveLengthTot;
821 chargeMax /= effectiveLengthMax;
822 };
823
824 // residual dE/dx correction on tgl and sector plane
826 corrTot = mCalibCont.getResidualCorrection(row.stackID, ChargeType::Tot, row.trackSnapshot.getTgl(), row.trackSnapshot.getSnp());
827 corrMax = mCalibCont.getResidualCorrection(row.stackID, ChargeType::Max, row.trackSnapshot.getTgl(), row.trackSnapshot.getSnp());
828 if (corrTot > 0) {
829 chargeTot /= corrTot;
830 };
831 if (corrMax > 0) {
832 chargeMax /= corrMax;
833 };
834 };
835
836 // residual gain map
838 gainResidual = sample->gainResidual;
839 chargeTot /= gainResidual;
840 chargeMax /= gainResidual;
841 };
842
843 // space-charge dEdx corrections
844 const float time = sample->cl.getTime() - trackTime0; // ToDo: get correct time from ITS-TPC track if possible
846 scCorr = mSCdEdxCorrection.getCorrection(time, row.sectorIndex, row.rowIndex, sample->pad);
847 if (scCorr > 0) {
848 chargeTot /= scCorr;
849 };
850 if (scCorr > 0) {
851 chargeMax /= scCorr;
852 };
853 }
854
855 // for debugging
856 if (mDebug) {
857 const o2::gpu::GPUTPCGeometry gpuGeom;
858 const float localX = gpuGeom.Row2X(row.rowIndex);
859 const float localY = gpuGeom.LinearPad2Y(row.sectorIndex, row.rowIndex, sample->cl.getPad());
860 const LocalPosition2D l2D{localX, localY};
861 const auto g2D = Mapper::LocalToGlobal(l2D, Sector(row.sectorIndex));
862 const float globalX = g2D.x();
863 const float globalY = g2D.y();
864
865 // slice to the base parametrization (X, alpha, params, covariance) instead of the full TrackTPC, since only the parametrization changes cluster-to-cluster after refit/propagation
866 const o2::track::TrackParCov trackParam = row.trackSnapshot;
867
868 (*debugStreamer) << "dEdxDebugCl"
869 << "trackIndex=" << mDebugTrackIndex
870 << "trackParam=" << trackParam
871 << "cl=" << sample->cl
872 << "chargeTot=" << chargeTot
873 << "chargeMax=" << chargeMax
874 << "excludeCl=" << excludeCl
875 << "region=" << row.region
876 << "rowIndex=" << row.rowIndex
877 << "sectorIndex=" << row.sectorIndex
878 << "stack=" << row.stackNumber
879 << "localX=" << localX
880 << "localY=" << localY
881 << "globalX=" << globalX
882 << "globalY=" << globalY
883 << "isShared=" << sample->isShared
884 << "isCombined=" << isCombined
885 << "refitFellBack=" << row.refitFellBack
886 << "topologyCorr=" << effectiveLength
887 << "topologyCorrTot=" << effectiveLengthTot
888 << "topologyCorrMax=" << effectiveLengthMax
889 << "gain=" << gain
890 << "fullGain=" << sample->gain
891 << "gainResidual=" << gainResidual
892 << "residualCorrTot=" << corrTot
893 << "residualCorrMax=" << corrMax
894 << "scCorr=" << scCorr
895 << "occupancy=" << sample->occupancy
896 << "inputClusterIndices=" << row.inputClusterIndices
897 << "\n";
898 };
899
900 if (excludeCl != 0) {
901 continue;
902 }
903
904 // set the region's min charge, only from clusters actually included in the dEdx calculation,
905 // so excluded clusters (edge, failed propagation, ...) don't bias the virtual charge used for subthreshold filling
906 if (chargeTot < minChargeTotROC[row.stackNumber]) {
907 minChargeTotROC[row.stackNumber] = chargeTot;
908 };
909
910 if (chargeMax < minChargeMaxROC[row.stackNumber]) {
911 minChargeMaxROC[row.stackNumber] = chargeMax;
912 };
913
914 if (row.stack == GEMstack::IROCgem) {
915 chargeTotROC[0].emplace_back(chargeTot);
916 chargeMaxROC[0].emplace_back(chargeMax);
917 nClsROC[0]++;
918 } else if (row.stack == GEMstack::OROC1gem) {
919 chargeTotROC[1].emplace_back(chargeTot);
920 chargeMaxROC[1].emplace_back(chargeMax);
921 nClsROC[1]++;
922 } else if (row.stack == GEMstack::OROC2gem) {
923 chargeTotROC[2].emplace_back(chargeTot);
924 chargeMaxROC[2].emplace_back(chargeMax);
925 nClsROC[2]++;
926 } else if (row.stack == GEMstack::OROC3gem) {
927 chargeTotROC[3].emplace_back(chargeTot);
928 chargeMaxROC[3].emplace_back(chargeMax);
929 nClsROC[3]++;
930 };
931
932 chargeTotROC[4].emplace_back(chargeTot);
933 chargeMaxROC[4].emplace_back(chargeMax);
934 }
935 }
936
937 // fill subthreshold clusters if not excluded
939 float cappedMinChargeTotROC[4], cappedMinChargeMaxROC[4];
940 for (int roc = 0; roc < 4; roc++) {
941 cappedMinChargeTotROC[roc] = (minChargeTotROC[roc] >= kNoValidCharge) ? minChargeTotROC[roc] : std::min(minChargeTotROC[roc], settings.maxSubthresholdChargeTot);
942 cappedMinChargeMaxROC[roc] = (minChargeMaxROC[roc] >= kNoValidCharge) ? minChargeMaxROC[roc] : std::min(minChargeMaxROC[roc], settings.maxSubthresholdChargeMax);
943 // a ROC with no valid accepted-cluster charge at all makes fillMissingClusters() below skip it entirely
944 // (nothing is pushed into chargeTotROC/chargeMaxROC for it) -- so the gaps counted for this ROC earlier
945 // in the row loop must be un-counted here too, or NHits*/NHitsSubThreshold* and the
946 // mNSubThresholdFilledPerSettings diagnostic would report fills that never actually happened
947 if (minChargeTotROC[roc] >= kNoValidCharge || minChargeMaxROC[roc] >= kNoValidCharge) {
948 nClsROC[roc] -= nClsSubThreshROC[roc];
949 nClsSubThreshROC[roc] = 0;
950 }
951 }
952 fillMissingClusters(nClsSubThreshROC, cappedMinChargeTotROC, cappedMinChargeMaxROC, settings.subthresholdMethod, chargeTotROC, chargeMaxROC);
953 if (mNSubThresholdFilledPerSettings.size() <= settingsIndex) {
954 mNSubThresholdFilledPerSettings.resize(settingsIndex + 1, 0);
955 }
956 mNSubThresholdFilledPerSettings[settingsIndex] += nClsSubThreshROC[0] + nClsSubThreshROC[1] + nClsSubThreshROC[2] + nClsSubThreshROC[3];
957 }
958
959 // number of clusters
960 output.NHitsSubThresholdIROC = nClsROC[0];
961 output.NHitsSubThresholdOROC1 = nClsROC[1];
962 output.NHitsSubThresholdOROC2 = nClsROC[2];
963 output.NHitsSubThresholdOROC3 = nClsROC[3];
964
965 // the gaps found above are always treated as subthreshold clusters (except a ROC with no valid charge to fill
966 // them with at all, un-counted above so it isn't double-reported as both "hit" and "subthreshold hit")
967 output.NHitsIROC = nClsROC[0] - nClsSubThreshROC[0];
968 output.NHitsOROC1 = nClsROC[1] - nClsSubThreshROC[1];
969 output.NHitsOROC2 = nClsROC[2] - nClsSubThreshROC[2];
970 output.NHitsOROC3 = nClsROC[3] - nClsSubThreshROC[3];
971
972 // copy corrected cluster charges
973 auto chargeTotVector = mDebug ? chargeTotROC[4] : std::vector<float>();
974 auto chargeMaxVector = mDebug ? chargeMaxROC[4] : std::vector<float>();
975
976 // calculate dEdx
977 output.dEdxTotIROC = getTruncMean(chargeTotROC[0], settings.low, settings.high);
978 output.dEdxTotOROC1 = getTruncMean(chargeTotROC[1], settings.low, settings.high);
979 output.dEdxTotOROC2 = getTruncMean(chargeTotROC[2], settings.low, settings.high);
980 output.dEdxTotOROC3 = getTruncMean(chargeTotROC[3], settings.low, settings.high);
981 output.dEdxTotTPC = getTruncMean(chargeTotROC[4], settings.low, settings.high);
982
983 output.dEdxMaxIROC = getTruncMean(chargeMaxROC[0], settings.low, settings.high);
984 output.dEdxMaxOROC1 = getTruncMean(chargeMaxROC[1], settings.low, settings.high);
985 output.dEdxMaxOROC2 = getTruncMean(chargeMaxROC[2], settings.low, settings.high);
986 output.dEdxMaxOROC3 = getTruncMean(chargeMaxROC[3], settings.low, settings.high);
987 output.dEdxMaxTPC = getTruncMean(chargeMaxROC[4], settings.low, settings.high);
988
989 // for debugging: one row per track, with the track as it was before refit/propagation touched it, per-cluster rows were already written to the "dEdxDebugCl" tree above (each with its own propagated track parameters) and can be grouped back to this row via trackIndex
990 if (mDebug) {
991 float minChargeTot = minChargeTotROC[0], minChargeMax = minChargeMaxROC[0];
992 for (int roc = 1; roc < 4; roc++) {
993 minChargeTot = (minChargeTotROC[roc] < minChargeTot) ? minChargeTotROC[roc] : minChargeTot;
994 minChargeMax = (minChargeMaxROC[roc] < minChargeMax) ? minChargeMaxROC[roc] : minChargeMax;
995 }
996 const MCCompLabel label = mcLabel ? *mcLabel : MCCompLabel{};
997 (*debugStreamer) << "dEdxDebugTrack"
998 << "trackIndex=" << mDebugTrackIndex
999 << "track=" << trackOrig
1000 << "output=" << output
1001 << "averageOcc=" << averageOcc
1002 << "nCl=" << rowData.size()
1003 << "minChargeTot=" << minChargeTot
1004 << "minChargeMax=" << minChargeMax
1005 << "chargeTotVector=" << chargeTotVector
1006 << "chargeMaxVector=" << chargeMaxVector
1007 << "occupancy=" << occupancyVector
1008 << "mcLabel=" << label
1009 << "\n";
1010 }
1011}
1012
1013void CalculatedEdx::calculatedEdxMultipleSettings(o2::tpc::TrackTPC& track, std::vector<dEdxInfo>& outputs, AverageOccupancy& averageOcc, const std::vector<dEdxSettings>& settingsList, const MCCompLabel* mcLabel)
1014{
1015 outputs.clear();
1016 if (settingsList.empty()) {
1017 return;
1018 }
1019
1020 o2::tpc::TrackTPC trackOrig;
1021 if (mDebug) {
1022 trackOrig = track; // pristine track, before refit/propagation mutates it cluster-by-cluster below
1023 }
1024 const float trackTime0 = track.getTime0(); // unaffected by refit/propagation, so it is the same for every row and every settings entry
1025
1026 // gather the per-row cluster/track data once, performing the refit/propagation to each cluster row exactly once; this also fills averageOcc, which does not depend on the dEdx settings and is therefore shared by every settings entry
1027 std::vector<RowClusterData> rowData;
1028 gatherRowClusterData(track, rowData, averageOcc);
1029
1030 // evaluate each settings entry against the shared row data
1031 outputs.resize(settingsList.size());
1032 for (size_t i = 0; i < settingsList.size(); ++i) {
1033 calculatedEdxFromRowData(rowData, settingsList[i], i, trackTime0, trackOrig, averageOcc, outputs[i], mcLabel);
1034 }
1035}
1036
1037void CalculatedEdx::calculatedEdx(o2::tpc::TrackTPC& track, const std::vector<o2::tpc::ClusterNative>& clusters, const ClInfoVec& clusterInfos, dEdxInfo& output, AverageOccupancy& averageOcc, float low, float high, CorrectionFlags correctionMask, ClusterFlags clusterMask, int subthresholdMethod, int stackBoundaryMethod, const char* debugRootFile, float maxSubthresholdChargeTot, float maxSubthresholdChargeMax, int sameRowClusterMethod)
1038{
1039 dEdxSettings settings;
1040 settings.low = low;
1041 settings.high = high;
1042 settings.correctionMask = correctionMask;
1043 settings.clusterMask = clusterMask;
1044 settings.subthresholdMethod = subthresholdMethod;
1045 settings.stackBoundaryMethod = stackBoundaryMethod;
1046 settings.debugRootFile = debugRootFile;
1047 settings.maxSubthresholdChargeTot = maxSubthresholdChargeTot;
1048 settings.maxSubthresholdChargeMax = maxSubthresholdChargeMax;
1049 settings.sameRowClusterMethod = sameRowClusterMethod;
1050
1051 o2::tpc::TrackTPC trackOrig;
1052 if (mDebug) {
1053 trackOrig = track; // pristine track, before refit/propagation mutates it cluster-by-cluster below
1054 }
1055 const float trackTime0 = track.getTime0();
1056
1057 std::vector<RowClusterData> rowData;
1058 gatherRowClusterData(track, clusters, clusterInfos, rowData, averageOcc);
1059
1060 calculatedEdxFromRowData(rowData, settings, 0, trackTime0, trackOrig, averageOcc, output);
1061}
1062
1063void CalculatedEdx::calculatedEdxMultipleSettings(o2::tpc::TrackTPC& track, const std::vector<o2::tpc::ClusterNative>& clusters, const ClInfoVec& clusterInfos, std::vector<dEdxInfo>& outputs, AverageOccupancy& averageOcc, const std::vector<dEdxSettings>& settingsList, const MCCompLabel* mcLabel)
1064{
1065 outputs.clear();
1066 if (settingsList.empty()) {
1067 return;
1068 }
1069
1070 o2::tpc::TrackTPC trackOrig;
1071 if (mDebug) {
1072 trackOrig = track; // pristine track, before refit/propagation mutates it cluster-by-cluster below
1073 }
1074 const float trackTime0 = track.getTime0(); // unaffected by refit/propagation, so it is the same for every row and every settings entry
1075
1076 // gather the per-row cluster/track data once, performing the refit/propagation to each cluster row exactly once; this also fills averageOcc, which does not depend on the dEdx settings and is therefore shared by every settings entry
1077 std::vector<RowClusterData> rowData;
1078 gatherRowClusterData(track, clusters, clusterInfos, rowData, averageOcc);
1079
1080 // evaluate each settings entry against the shared row data
1081 outputs.resize(settingsList.size());
1082 for (size_t i = 0; i < settingsList.size(); ++i) {
1083 calculatedEdxFromRowData(rowData, settingsList[i], i, trackTime0, trackOrig, averageOcc, outputs[i], mcLabel);
1084 }
1085}
1086
1087void CalculatedEdx::calculatedEdx(o2::tpc::TrackTPC& track, dEdxInfo& output, AverageOccupancy& averageOcc, float low, float high, CorrectionFlags correctionMask, ClusterFlags clusterMask, int subthresholdMethod, int stackBoundaryMethod, const char* debugRootFile, float maxSubthresholdChargeTot, float maxSubthresholdChargeMax, int sameRowClusterMethod)
1088{
1089 dEdxSettings settings;
1090 settings.low = low;
1091 settings.high = high;
1092 settings.correctionMask = correctionMask;
1093 settings.clusterMask = clusterMask;
1094 settings.subthresholdMethod = subthresholdMethod;
1095 settings.stackBoundaryMethod = stackBoundaryMethod;
1096 settings.debugRootFile = debugRootFile;
1097 settings.maxSubthresholdChargeTot = maxSubthresholdChargeTot;
1098 settings.maxSubthresholdChargeMax = maxSubthresholdChargeMax;
1099 settings.sameRowClusterMethod = sameRowClusterMethod;
1100
1101 o2::tpc::TrackTPC trackOrig;
1102 if (mDebug) {
1103 trackOrig = track; // pristine track, before refit/propagation mutates it cluster-by-cluster below
1104 }
1105 const float trackTime0 = track.getTime0();
1106
1107 std::vector<RowClusterData> rowData;
1108 gatherRowClusterData(track, rowData, averageOcc);
1109
1110 calculatedEdxFromRowData(rowData, settings, 0, trackTime0, trackOrig, averageOcc, output);
1111}
1112
1113float CalculatedEdx::getTruncMean(std::vector<float>& charge, float low, float high) const
1114{
1115 // sort the charge vector
1116 std::sort(charge.begin(), charge.end());
1117
1118 // calculate truncated mean
1119 int nCl = 0;
1120 float sum = 0;
1121 size_t firstCl = charge.size() * low;
1122 size_t lastCl = charge.size() * high;
1123
1124 for (size_t iCl = firstCl; iCl < lastCl; ++iCl) {
1125 sum += charge[iCl];
1126 ++nCl;
1127 }
1128
1129 if (nCl > 0) {
1130 sum /= nCl;
1131 }
1132 // if nCl == 0 (charge was empty, or too few entries for low/high to select any index), sum stays 0
1133 return sum;
1134}
1135
1136float CalculatedEdx::getTrackTopologyCorrection(const o2::tpc::TrackTPC& track, const unsigned int region) const
1137{
1138 const float padLength = Mapper::instance().getPadRegionInfo(region).getPadHeight();
1139 const float snp = track.getSnp();
1140 const float tgl = track.getTgl();
1141 const float snp2 = snp * snp;
1142 const float tgl2 = tgl * tgl;
1143 // calculate the trace length of the track over the pad
1144 const float effectiveLength = padLength * std::sqrt((1 + tgl2) / (1 - snp2));
1145 return effectiveLength;
1146}
1147
1148float CalculatedEdx::getTrackTopologyCorrectionPol(const o2::tpc::TrackTPC& track, const o2::tpc::ClusterNative& cl, const unsigned int region, const float charge, ChargeType chargeType, const float threshold) const
1149{
1150 const float snp = std::abs(track.getSnp());
1151 const float tgl = track.getTgl();
1152 constexpr float maxSnp2 = 0.99f;
1153 float snp2 = snp * snp;
1154 if (snp2 > maxSnp2) {
1155 snp2 = maxSnp2;
1156 }
1157 const float tgl2 = tgl * tgl;
1158 const float sec2 = 1.f / (1.f - snp2);
1159 const float tanTheta = std::sqrt(tgl2 * sec2);
1160
1161 const float z = std::abs(track.getParam(1));
1162 const float padTmp = cl.getPad();
1163 const float absRelPad = std::abs(padTmp - int(padTmp + 0.5f));
1164 const float relTime = cl.getTime() - int(cl.getTime() + 0.5f);
1165
1166 const float effectiveLength = mCalibCont.getTopologyCorrection(region, chargeType, tanTheta, snp, z, absRelPad, relTime, threshold, charge);
1167 return effectiveLength;
1168}
1169
1170unsigned int CalculatedEdx::getOccupancy(float clTime) const
1171{
1172 // occupancy is only meaningful when the refit method is used, since mTPCRefitterOccMap is only filled by setRefit()
1173 const int nTimeBinsPerOccupBin = 16;
1174 const int iBinOcc = clTime / nTimeBinsPerOccupBin + 2;
1175 if (!mRefit || iBinOcc < 0 || static_cast<size_t>(iBinOcc) >= mTPCRefitterOccMap.size()) {
1176 return -1;
1177 }
1178 return mTPCRefitterOccMap[iBinOcc];
1179}
1180
1181bool CalculatedEdx::isInStackBoundaries(int stackNumber, unsigned char rowIndex, int stackBoundaryMethod)
1182{
1183 // retrieve boundaries for the given stack
1184 const auto& boundaries = mStackBoundaries[stackNumber];
1185 // check direct match for method 1 or 2
1186 for (unsigned char boundary : boundaries) {
1187 if (rowIndex == boundary) {
1188 return true;
1189 }
1190 }
1191 // additional checks for method 2
1192 if (stackBoundaryMethod == 2) {
1193 if (rowIndex == boundaries[0] + 1 || rowIndex == boundaries[1] - 1) {
1194 return true;
1195 }
1196 }
1197 return false;
1198}
1199
1200void CalculatedEdx::loadCalibsFromCCDB(long runNumberOrTimeStamp, const bool isMC, const bool loadSCCorrMap, const bool useSCCorrMap, const bool useVDrift, const float corrMapLumiScale)
1201{
1202 // setup CCDB manager
1204 cm.setURL("http://alice-ccdb.cern.ch/");
1205
1206 auto tRun = runNumberOrTimeStamp;
1207 if (runNumberOrTimeStamp < 10000000) {
1208 auto runDuration = cm.getRunDuration(runNumberOrTimeStamp);
1209 tRun = runDuration.first + (runDuration.second - runDuration.first) / 2; // time stamp for the middle of the run duration
1210 }
1211 LOGP(info, "Timestamp: {}", tRun);
1212 cm.setTimestamp(tRun);
1213
1214 // set the track topology correction
1216 o2::tpc::CalibdEdxTrackTopologyPol calibTrackTopology;
1217 calibTrackTopology.setFromContainer(*calibTrackTopologyContainer);
1218 mCalibCont.setPolTopologyCorrection(calibTrackTopology);
1219
1220 // set the gain map
1222 const o2::tpc::CalDet<float> gainMapResidual = (*cm.getForTimeStamp<std::unordered_map<std::string, o2::tpc::CalDet<float>>>(o2::tpc::CDBTypeMap.at(o2::tpc::CDBType::CalPadGainResidual), tRun))["GainMap"];
1223
1224 const float minGain = 0;
1225 const float maxGain = 2;
1226 mCalibCont.setGainMap(*gainMap, minGain, maxGain);
1227 mCalibCont.setGainMapResidual(gainMapResidual);
1228
1229 // set the residual dEdx correction
1231
1232 const auto* residualCorr = static_cast<o2::tpc::CalibdEdxCorrection*>(residualObj);
1233 mCalibCont.setResidualCorrection(*residualCorr);
1234
1235 // set the zero supression threshold map
1236 std::unordered_map<std::string, o2::tpc::CalDet<float>>* zeroSupressionThresholdMap = cm.getForTimeStamp<std::unordered_map<std::string, o2::tpc::CalDet<float>>>(o2::tpc::CDBTypeMap.at(o2::tpc::CDBType::ConfigFEEPad), tRun);
1237 mCalibCont.setZeroSupresssionThreshold(zeroSupressionThresholdMap->at("ThresholdMap"));
1238
1239 // set the magnetic field
1240 auto magField = cm.get<o2::parameters::GRPMagField>("GLO/Config/GRPMagField");
1242 float bz = GPUO2InterfaceUtils::getNominalGPUBz(*magField);
1243 LOGP(info, "Magnetic field: {}", bz);
1245
1246 // set the propagator
1247 auto propagator = o2::base::Propagator::Instance();
1249 propagator->setMatLUT(matLut);
1250
1251 // the space-charge and drift corrections are optional: a missing object leaves the default (no space-charge correction, nominal drift velocity and t0)
1252 const bool prevFatalWhenNull = cm.getFatalWhenNull();
1253 cm.setFatalWhenNull(false);
1254
1255 // load the space-charge correction maps
1256 const auto avgMapType = isMC ? o2::tpc::CDBType::CalCorrMapMC : o2::tpc::CDBType::CalCorrMap;
1257 o2::gpu::TPCFastTransform* avgMap = nullptr;
1258 if (loadSCCorrMap || useSCCorrMap) {
1259 avgMap = cm.getForTimeStamp<o2::gpu::TPCFastTransform>(o2::tpc::CDBTypeMap.at(avgMapType), tRun);
1260 if (avgMap) {
1262 } else {
1263 LOGP(warning, "no {} at {}: no space-charge correction", o2::tpc::CDBTypeMap.at(avgMapType), tRun);
1264 }
1265 }
1266
1267 if (loadSCCorrMap && avgMap) {
1269 if (auto* derMap = cm.getForTimeStamp<o2::gpu::TPCFastTransform>(o2::tpc::CDBTypeMap.at(derMapType), tRun)) {
1270 derMap->rectifyAfterReadingFromFile();
1271 mSCdEdxCorrection.setCorrectionMaps(avgMap, derMap);
1272 } else {
1273 LOGP(warning, "no {} at {}: the dE/dx space-charge correction is not available", o2::tpc::CDBTypeMap.at(derMapType), tRun);
1274 }
1275 }
1276
1277 // cluster transform used by the refit and for the cluster x: the space-charge map, for data scaled to the luminosity with the reference map as in the reconstruction
1278 if (useSCCorrMap && avgMap) {
1279 const o2::gpu::TPCFastTransform* refMap = nullptr;
1280 float lumiScale = corrMapLumiScale;
1281 if (!isMC) {
1282 if (auto* ref = cm.getForTimeStamp<o2::gpu::TPCFastTransform>(o2::tpc::CDBTypeMap.at(o2::tpc::CDBType::CalCorrMapRef), tRun)) {
1284 refMap = ref;
1285 if (lumiScale < 0) {
1286 lumiScale = getTPCScalerLumiScale(tRun, avgMap->getIDC());
1287 }
1288 } else {
1289 LOGP(warning, "no {} at {}: the space-charge map is not scaled to the luminosity", o2::tpc::CDBTypeMap.at(o2::tpc::CDBType::CalCorrMapRef), tRun);
1290 }
1291 }
1292 setTPCCorrMap(*avgMap, refMap, lumiScale);
1293 LOGP(info, "cluster transform: using CCDB space-charge correction map {}, lumi scale {}", o2::tpc::CDBTypeMap.at(avgMapType), (refMap && lumiScale >= 0) ? fmt::format("{}", lumiScale) : "none");
1294 } else {
1296 LOGP(info, "cluster transform: no space-charge correction");
1297 }
1298
1299 // drift velocity and time offset of the cluster transform
1300 if (useVDrift) {
1301 if (auto* vd = cm.getForTimeStamp<o2::tpc::VDriftCorrFact>(o2::tpc::CDBTypeMap.at(o2::tpc::CDBType::CalVDriftTgl), tRun)) {
1302 setTPCVDrift(*vd);
1303 LOGP(info, "cluster transform vDrift calib: corrFact={:.5f} refVDrift={:.5f} timeOffset={:.4f}us", vd->corrFact, vd->refVDrift, vd->getTimeOffset());
1304 } else {
1305 LOGP(warning, "no {} at {}: cluster transform at nominal vDrift/t0", o2::tpc::CDBTypeMap.at(o2::tpc::CDBType::CalVDriftTgl), tRun);
1306 }
1307 } else {
1308 LOGP(info, "cluster transform: nominal vDrift/t0");
1309 }
1310 cm.setFatalWhenNull(prevFatalWhenNull);
1311
1312 // set the dead channel map
1313 o2::tpc::DeadChannelMapCreator deadCMCreator;
1314 deadCMCreator.init();
1315 deadCMCreator.load(tRun);
1316 const o2::tpc::CalDet<bool>& deadMap = deadCMCreator.getDeadChannelMap();
1317 mCalibCont.setDeadChannelMap(deadMap);
1318}
1319
1320float CalculatedEdx::getTPCScalerLumiScale(long timeStamp, float meanIDC) const
1321{
1322 // mean scaler of both sides (weighted over the ion drift time) over the map's mean IDC
1324 const bool prevFatalWhenNull = cm.getFatalWhenNull();
1325 cm.setFatalWhenNull(false);
1326 auto* scalerTree = cm.getForTimeStamp<TTree>(o2::tpc::CDBTypeMap.at(o2::tpc::CDBType::CalScaler), timeStamp);
1328 cm.setFatalWhenNull(prevFatalWhenNull);
1329 if (!scalerTree || meanIDC <= 0) {
1330 LOGP(warning, "no TPC scaler or mean IDC of the map ({}) at {}, the space-charge map is not scaled", meanIDC, timeStamp);
1331 return -1.f;
1332 }
1333 o2::tpc::TPCScaler scaler;
1334 scaler.setFromTree(*scalerTree);
1335 if (weights && weights->isValid()) {
1336 scaler.setScalerWeights(*weights);
1337 scaler.useWeights(true);
1338 const float integrationTimeMS = std::min(std::abs(weights->mFirstTimeStampMS), 500.f);
1339 if (integrationTimeMS > 0) {
1340 scaler.setIonDriftTimeMS(integrationTimeMS);
1341 }
1342 // the weights are only defined for a full window before the timestamp, otherwise use the unweighted mean of the closest window
1343 const double endMS = std::min(scaler.getEndTimeStampMS(o2::tpc::Side::A), scaler.getEndTimeStampMS(o2::tpc::Side::C));
1344 if (static_cast<double>(timeStamp) - scaler.getIonDriftTimeMS() < scaler.getStartTimeStampMS() || timeStamp > endMS) {
1345 LOGP(warning, "time stamp {} not covered by the TPC scaler window [{}, {}] ms, using the unweighted mean scaler", timeStamp, scaler.getStartTimeStampMS() + scaler.getIonDriftTimeMS(), endMS);
1346 scaler.useWeights(false);
1347 }
1348 }
1349 const float instIDC = (scaler.getMeanScaler(timeStamp, o2::tpc::Side::A) + scaler.getMeanScaler(timeStamp, o2::tpc::Side::C)) / 2;
1350 LOGP(info, "TPC scaler at {}: {}, map mean IDC: {}", timeStamp, instIDC, meanIDC);
1351 return instIDC > 0 ? instIDC / meanIDC : -1.f;
1352}
1353
1354void CalculatedEdx::loadCalibsFromLocalCCDBFolder(const char* localCCDBFolder, const bool useSCCorrMap, const bool useVDrift)
1355{
1356 setTrackTopologyCorrectionFromFile(localCCDBFolder, "/TPC/Calib/TopologyGainPiecewise/snapshot.root", "ccdb_object");
1357 setGainMapFromFile(localCCDBFolder, "/TPC/Calib/PadGainFull/snapshot.root", "ccdb_object");
1358 setGainMapResidualFromFile(localCCDBFolder, "/TPC/Calib/PadGainResidual/snapshot.root", "ccdb_object");
1359 setResidualCorrectionFromFile(localCCDBFolder, "/TPC/Calib/TimeGain/snapshot.root", "ccdb_object");
1360 setZeroSuppressionThresholdFromFile(localCCDBFolder, "/TPC/Config/FEEPad/snapshot.root", "ccdb_object");
1361 setMagneticFieldFromFile(localCCDBFolder, "/GLO/Config/GRPMagField/snapshot.root", "ccdb_object");
1362 setPropagatorFromFile(localCCDBFolder, "/GLO/Param/MatLUT/snapshot.root", "ccdb_object");
1363 // optional, a missing file leaves the default (no space-charge correction, nominal vDrift/t0)
1364 if (useSCCorrMap) {
1365 setTPCCorrMapFromFile(localCCDBFolder, "/TPC/Calib/CorrectionMapV2/snapshot.root", "ccdb_object");
1366 } else {
1368 }
1369 if (useVDrift) {
1370 setVDriftFromFile(localCCDBFolder, "/TPC/Calib/VDriftTgl/snapshot.root", "ccdb_object");
1371 }
1372}
1373
1374void CalculatedEdx::setTPCCorrMapFromFile(const char* folder, const char* file, const char* object)
1375{
1376 std::unique_ptr<TFile> corrMapFile(TFile::Open(fmt::format("{}{}", folder, file).data()));
1377 auto* corrMap = (corrMapFile && !corrMapFile->IsZombie()) ? (o2::gpu::TPCFastTransform*)corrMapFile->Get(object) : nullptr;
1378 if (!corrMap) {
1379 LOGP(warning, "no {} in {}{}: no space-charge correction", object, folder, file);
1381 return;
1382 }
1383 corrMap->rectifyAfterReadingFromFile();
1384 setTPCCorrMap(*corrMap);
1385 LOGP(info, "cluster transform: using space-charge correction map from {}", corrMapFile->GetName());
1386}
1387
1388void CalculatedEdx::setVDriftFromFile(const char* folder, const char* file, const char* object)
1389{
1390 std::unique_ptr<TFile> vdFile(TFile::Open(fmt::format("{}{}", folder, file).data()));
1391 if (!vdFile || vdFile->IsZombie()) {
1392 LOGP(warning, "no {}{}: cluster transform at nominal vDrift/t0", folder, file);
1393 return;
1394 }
1395 if (auto* vd = (o2::tpc::VDriftCorrFact*)vdFile->Get(object)) {
1396 setTPCVDrift(*vd);
1397 LOGP(info, "cluster transform vDrift calib from {}: corrFact={:.5f} refVDrift={:.5f} timeOffset={:.4f}us", vdFile->GetName(), vd->corrFact, vd->refVDrift, vd->getTimeOffset());
1398 }
1399}
1400
1401void CalculatedEdx::setTrackTopologyCorrectionFromFile(const char* folder, const char* file, const char* object)
1402{
1403 o2::tpc::CalibdEdxTrackTopologyPol calibTrackTopology;
1404 calibTrackTopology.loadFromFile(fmt::format("{}{}", folder, file).data(), object);
1405 mCalibCont.setPolTopologyCorrection(calibTrackTopology);
1406}
1407
1408void CalculatedEdx::setGainMapFromFile(const char* folder, const char* file, const char* object)
1409{
1410 std::unique_ptr<TFile> gainMapFile(TFile::Open(fmt::format("{}{}", folder, file).data()));
1411 if (!gainMapFile->IsZombie()) {
1412 LOGP(info, "Using file: {}", gainMapFile->GetName());
1413 o2::tpc::CalDet<float>* gainMap = (o2::tpc::CalDet<float>*)gainMapFile->Get(object);
1414 mCalibCont.setGainMap(*gainMap, 0., 2.);
1415 }
1416}
1417
1418void CalculatedEdx::setGainMapResidualFromFile(const char* folder, const char* file, const char* object)
1419{
1420 std::unique_ptr<TFile> gainMapResidualFile(TFile::Open(fmt::format("{}{}", folder, file).data()));
1421 if (!gainMapResidualFile->IsZombie()) {
1422 LOGP(info, "Using file: {}", gainMapResidualFile->GetName());
1423 std::unordered_map<std::string, o2::tpc::CalDet<float>>* gainMapResidual = (std::unordered_map<std::string, o2::tpc::CalDet<float>>*)gainMapResidualFile->Get(object);
1424 mCalibCont.setGainMapResidual(gainMapResidual->at("GainMap"));
1425 }
1426}
1427
1428void CalculatedEdx::setResidualCorrectionFromFile(const char* folder, const char* file, const char* object)
1429{
1430 std::unique_ptr<TFile> calibdEdxResidualFile(TFile::Open(fmt::format("{}{}", folder, file).data()));
1431 if (!calibdEdxResidualFile->IsZombie()) {
1432 LOGP(info, "Using file: {}", calibdEdxResidualFile->GetName());
1433 o2::tpc::CalibdEdxCorrection* calibdEdxResidual = (o2::tpc::CalibdEdxCorrection*)calibdEdxResidualFile->Get(object);
1434 mCalibCont.setResidualCorrection(*calibdEdxResidual);
1435 }
1436}
1437
1438void CalculatedEdx::setZeroSuppressionThresholdFromFile(const char* folder, const char* file, const char* object)
1439{
1440 std::unique_ptr<TFile> zeroSuppressionFile(TFile::Open(fmt::format("{}{}", folder, file).data()));
1441 if (!zeroSuppressionFile->IsZombie()) {
1442 LOGP(info, "Using file: {}", zeroSuppressionFile->GetName());
1443 std::unordered_map<std::string, o2::tpc::CalDet<float>>* zeroSupressionThresholdMap = (std::unordered_map<std::string, o2::tpc::CalDet<float>>*)zeroSuppressionFile->Get(object);
1444 mCalibCont.setZeroSupresssionThreshold(zeroSupressionThresholdMap->at("ThresholdMap"));
1445 }
1446}
1447
1448void CalculatedEdx::setMagneticFieldFromFile(const char* folder, const char* file, const char* object)
1449{
1450 std::unique_ptr<TFile> magFile(TFile::Open(fmt::format("{}{}", folder, file).data()));
1451 if (!magFile->IsZombie()) {
1452 LOGP(info, "Using file: {}", magFile->GetName());
1453 o2::parameters::GRPMagField* magField = (o2::parameters::GRPMagField*)magFile->Get(object);
1455 float bz = GPUO2InterfaceUtils::getNominalGPUBz(*magField);
1456 LOGP(info, "Magnetic field: {}", bz);
1458 }
1459}
1460
1461void CalculatedEdx::setPropagatorFromFile(const char* folder, const char* file, const char* object)
1462{
1463 auto propagator = o2::base::Propagator::Instance();
1464 std::unique_ptr<TFile> matLutFile(TFile::Open(fmt::format("{}{}", folder, file).data()));
1465 if (!matLutFile->IsZombie()) {
1466 LOGP(info, "Using file: {}", matLutFile->GetName());
1468 propagator->setMatLUT(matLut);
1469 }
1470}
Simple interface to the CDB manager.
Class of a TPC cluster in TPC-native coordinates (row, time)
int16_t charge
Definition RawEventData.h:5
int16_t time
Definition RawEventData.h:4
std::vector< double > sum
int32_t i
#define failed(...)
Definition Utils.h:42
Header of the General Run Parameters object for B field values.
char error[CURL_ERROR_SIZE]
float chi2
std::vector< int > clusterIndices
void output(const std::map< std::string, ChannelStat > &channels)
Definition rawdump.cxx:197
uint32_t roc
Definition RawData.h:3
uint32_t stack
Definition RawData.h:1
class to create TPC fast space charge correction
class to create TPC fast transformation
int nClusters
static MatLayerCylSet * rectifyPtrFromFile(MatLayerCylSet *ptr)
static int initFieldFromGRP(const o2::parameters::GRPMagField *grp, bool verbose=false)
GPUd() value_type estimateLTFast(o2 static GPUd() float estimateLTIncrement(const o2 PropagatorImpl * Instance(bool uninitialized=false)
Definition Propagator.h:180
static BasicCCDBManager & instance()
static size_t fillOccupancyMapGetSize(uint32_t nHbfPerTf, const GPUParam *param=nullptr)
static void fillSharedClustersAndOccupancyMap(const o2::tpc::ClusterNativeAccess *cl, const gsl::span< const o2::tpc::TrackTPC > trks, const o2::tpc::TPCClRefElem *trackRef, uint8_t *shmap, uint32_t *ocmap=nullptr, uint32_t nHbfPerTf=0, const GPUParam *param=nullptr)
static float getNominalGPUBz(T &src)
static TPCFastTransformPOD * create(aligned_unique_buffer_ptr< TPCFastTransformPOD > &destVector, const TPCFastTransform &src)
Create POD transform from old flat-buffer one. Provided vector will serve as a buffer.
TPCFastSpaceChargeCorrection & getCorrection()
Gives a reference for external initialization of TPC corrections.
GEMstack gemStack() const
Definition CRU.h:82
void loadCalibsFromCCDB(long runNumberOrTimeStamp, const bool isMC=false, const bool loadSCCorrMap=true, const bool useSCCorrMap=true, const bool useVDrift=true, const float corrMapLumiScale=-1.f)
void setFieldNominalGPUBz(const float field)
void setVDriftFromFile(const char *folder, const char *file, const char *object)
load the drift-velocity calibration (VDriftCorrFact) from a local file and rebuild mTPCCorrMap
void calculatedEdx(TrackTPC &track, dEdxInfo &output, AverageOccupancy &averageOcc, float low=2.f/128.f, float high=77.f/128.f, CorrectionFlags correctionMask=CorrectionFlags::TopologyPol|CorrectionFlags::dEdxResidual, ClusterFlags clusterMask=ClusterFlags::None, int subthresholdMethod=0, int stackBoundaryMethod=0, const char *debugRootFile="dEdxDebug.root", float maxSubthresholdChargeTot=100000.f, float maxSubthresholdChargeMax=100000.f, int sameRowClusterMethod=2)
void setTPCCorrMap(const o2::gpu::TPCFastTransform &corrMap, const o2::gpu::TPCFastTransform *corrMapRef=nullptr, float lumiScale=-1.f)
void setTrackTopologyCorrectionFromFile(const char *folder, const char *file, const char *object)
void setZeroSuppressionThresholdFromFile(const char *folder, const char *file, const char *object)
void setPropagatorFromFile(const char *folder, const char *file, const char *object)
unsigned int getOccupancy(float clTime) const
void setRefit(const unsigned int nHbfPerTf=32)
set the refitter
float getTrackTopologyCorrectionPol(const o2::tpc::TrackTPC &track, const o2::tpc::ClusterNative &cl, const unsigned int region, const float charge, ChargeType chargeType, const float threshold) const
void calculatedEdxMultipleSettings(TrackTPC &track, std::vector< dEdxInfo > &outputs, AverageOccupancy &averageOcc, const std::vector< dEdxSettings > &settingsList, const MCCompLabel *mcLabel=nullptr)
float getTPCScalerLumiScale(long timeStamp, float meanIDC) const
void setMembers(std::vector< o2::tpc::TPCClRefElem > *tpcTrackClIdxVecInput, const o2::tpc::ClusterNativeAccess &clIndex, std::vector< o2::tpc::TrackTPC > *vTPCTracksArrayInp)
void setTPCCorrMapFromFile(const char *folder, const char *file, const char *object)
load the space-charge correction map for the cluster transform from a local file; without the file,...
void setTPCVDrift(const o2::tpc::VDriftCorrFact &v)
void loadCalibsFromLocalCCDBFolder(const char *localCCDBFolder, const bool useSCCorrMap=true, const bool useVDrift=true)
void fillMissingClusters(int missingClusters[4], const float minChargeTot[4], const float minChargeMax[4], int method, std::array< std::vector< float >, 5 > &chargeTotROC, std::array< std::vector< float >, 5 > &chargeMaxROC)
float getTruncMean(std::vector< float > &charge, float low, float high) const
void setResidualCorrectionFromFile(const char *folder, const char *file, const char *object)
float getTrackTopologyCorrection(const o2::tpc::TrackTPC &track, const unsigned int region) const
bool isInStackBoundaries(int stackNumber, unsigned char rowIndex, int stackBoundaryMethod)
void handleSameRowClusters(o2::tpc::TrackTPC &track, std::vector< std::pair< unsigned char, unsigned char > > &rowOrder, std::map< std::pair< unsigned char, unsigned char >, std::vector< int > > &clustersByRow, std::set< std::pair< unsigned char, unsigned char > > &mergeableRows, std::map< int, std::tuple< unsigned char, unsigned char, unsigned int > > &clusterReferencesByIndex)
void setMagneticFieldFromFile(const char *folder, const char *file, const char *object)
void setGainMapResidualFromFile(const char *folder, const char *file, const char *object)
void setStreamer(const char *debugRootFile)
void setGainMapFromFile(const char *folder, const char *file, const char *object)
void setGainMap(const CalDet< float > &gainMap, const float minGain, const float maxGain)
setting the gain map map from a CalDet
void setPolTopologyCorrection(const CalibdEdxTrackTopologyPol &calibTrackTopology)
void setDeadChannelMap(const CalDet< bool > &deadMap)
setting the gain map map from a CalDet
void setZeroSupresssionThreshold(const CalDet< float > &thresholdMap)
void setResidualCorrection(const CalibdEdxCorrection &residualCorr)
void setGainMapResidual(const CalDet< float > &gainMapResidual, const float minResidualGain=0.7f, const float maxResidualGain=1.3f)
setting the gain map map from a CalDet
calibration class for the track topology correction of the dE/dx using multvariate polynomials
void loadFromFile(const char *fileName, const char *name)
void setFromContainer(const CalibdEdxTrackTopologyPolContainer &container)
float getCorrection(const float time, unsigned char sector, unsigned char padrow, int pad) const
void setCorrectionMaps(o2::gpu::TPCFastTransform *avg, o2::gpu::TPCFastTransform *der, const float lumi)
void init(std::string_view url="")
const CalDet< bool > & getDeadChannelMap() const
static const std::vector< unsigned int > PADSPERROW[NREGIONS]
number of pads per row in region
Definition Mapper.h:567
static unsigned int getLocalRowFromGlobalRow(const unsigned int row)
Definition Mapper.h:72
static Mapper & instance(const std::string mappingDir="")
Definition Mapper.h:44
const PadRegionInfo & getPadRegionInfo(const unsigned char region) const
Definition Mapper.h:385
static constexpr unsigned int PADROWS
total number of pad rows
Definition Mapper.h:528
static GlobalPosition3D LocalToGlobal(const LocalPosition3D &pos, const double alpha)
Definition Mapper.h:461
GlobalPosition2D getPadCentre(const PadSecPos &padSec) const
Definition Mapper.h:163
static constexpr unsigned REGION[PADROWS]
region for global pad row
Definition Mapper.h:537
float getPadHeight() const
static constexpr int MAXSECTOR
Definition Sector.h:44
static TPCFastSpaceChargeCorrectionHelper * instance()
Singleton.
void addCorrections(o2::gpu::TPCFastSpaceChargeCorrection &mainCorrection, double scale, const std::vector< std::pair< const o2::gpu::TPCFastSpaceChargeCorrection *, double > > &additionalCorrections)
static TPCFastTransformHelperO2 * instance()
Singleton.
std::unique_ptr< TPCFastTransform > create(int64_t TimeStamp)
_______________ Main functionality ________________________
int updateCalibration(TPCFastTransform &fastTransform, int64_t TimeStamp, float vDriftFactor=1.f, float vDriftRef=0.f, float driftTimeOffset=0.f)
Updates the transformation with the new time stamp.
float getMeanScaler(double timestamp, o2::tpc::Side side) const
void setIonDriftTimeMS(float ionDriftTimeMS)
Definition TPCScaler.h:58
float getIonDriftTimeMS() const
Definition TPCScaler.h:103
double getStartTimeStampMS() const
Definition TPCScaler.h:112
double getEndTimeStampMS(o2::tpc::Side side) const
Definition TPCScaler.h:115
void useWeights(bool useWeights)
enable usage of weights
Definition TPCScaler.h:142
void setFromTree(TTree &tpcScalerTree)
set this object from input tree
void setScalerWeights(const TPCScalerWeights &weights)
setting the weights for the scalers
Definition TPCScaler.h:136
GLint GLenum GLint x
Definition glcorearb.h:403
GLsizei const GLuint const GLfloat * weights
Definition glcorearb.h:5475
GLuint buffer
Definition glcorearb.h:655
const GLdouble * v
Definition glcorearb.h:832
GLsizei samples
Definition glcorearb.h:1309
GLint y
Definition glcorearb.h:270
GLboolean * data
Definition glcorearb.h:298
GLuint GLsizei const GLchar * label
Definition glcorearb.h:2519
GLuint GLint GLboolean GLint GLenum access
Definition glcorearb.h:2196
GLint ref
Definition glcorearb.h:291
GLdouble GLdouble GLdouble z
Definition glcorearb.h:843
uint8_t int statusCode int
uint8_t itsSharedClusterMap uint8_t
void check(const std::vector< std::string > &arguments, const std::vector< ConfigParamSpec > &workflowOptions, const std::vector< DeviceSpec > &deviceSpecs, CheckMatrix &matrix)
o2::track::TrackParCov int int int float int nCl
const bool const int TrackITSInternal< NLayers > & track
Global TPC definitions and constants.
@ IROCgem
Definition Defs.h:55
@ OROC1gem
Definition Defs.h:56
@ OROC3gem
Definition Defs.h:58
@ OROC2gem
Definition Defs.h:57
@ ExcludeSingleCl
flag to exclude single clusters in dEdx calculation
@ ExcludeSubthresholdCl
flag to exclude subthreshold clusters in dEdx calculation
@ ExcludeSplitPadCl
flag to exclude split pad clusters in dEdx calculation
@ ExcludeEdgeCl
flag to exclude sector edge clusters in dEdx calculation
@ ExcludeSectorBoundaries
flag to exclude sector boundary clusters in subthreshold cluster treatment
@ ExcludeSplitCl
flag to exclude split pad or time clusters in dEdx calculation
@ ExcludeSplitTimeCl
flag to exclude split time clusters in dEdx calculation
@ ExcludeSamePadRowCl
flag to exclude clusters in the same pad row in dEdx calculation
@ ExcludeSharedCl
flag to exclude clusters shared between tracks in dEdx calculation
ChargeType
Definition Defs.h:72
@ Tot
Definition Defs.h:74
@ Max
Definition Defs.h:73
math_utils::Point2D< float > LocalPosition2D
Definition Defs.h:109
std::vector< ClInfo > ClInfoVec
const std::unordered_map< CDBType, const std::string > CDBTypeMap
Storage name in CCDB for each calibration and parameter type.
Definition CDBTypes.h:98
CorrectionFlags
dEdx calculation class
@ TopologyPol
flag for topology correction from polynomials
@ TopologySimple
flag for simple analytical topology correction
@ dEdxResidual
flag for residual dEdx correction
@ dEdxSC
flag for space-charge dEdx correction
@ GainFull
flag for full gain map from calibration container
@ GainResidual
flag for residuals gain map from calibration container
@ A
Definition Defs.h:37
@ C
Definition Defs.h:38
@ CalScaler
Scaler from IDCs or combined estimator.
@ CalVDriftTgl
ITS-TPC difTgl vdrift calibration.
@ CalScalerWeights
Weights for scalers.
@ CalCorrMapMC
Cluster correction map (high IR rate distortions) for MC.
@ CalPadGainFull
Full pad gain calibration.
@ ConfigFEEPad
FEE pad-by-pad configuration map.
@ CalPadGainResidual
ResidualpPad gain calibration (e.g. from tracks)
@ CalCorrMapRef
Cluster correction reference map (static distortions)
@ CalTimeGain
Gain variation over time.
@ CalTimeGainMC
Gain variation over time for MC.
@ CalCorrMap
Cluster correction map (high IR rate distortions)
uint32_t TPCClRefElem
TrackParCovF TrackParCov
Definition Track.h:33
average cluster occupancy of a track, per TPC region
simple struct to enable writing the MultivariatePolynomialCT to file
unsigned int clusterOffset[constants::MAXSECTOR][constants::MAXGLOBALPADROW]
uint8_t uint16_t pad
GEM stack identification.
Definition Defs.h:79
bundles the settings of one calculatedEdx() call (everything except the track/output/averageOcc) used...
float maxSubthresholdChargeMax
upper limit for the per-region minimum qMax used as the virtual charge of a subthreshold cluster (def...
CorrectionFlags correctionMask
corrections to apply
unsigned short subthresholdMethod
subthreshold cluster charge filling method
std::string debugRootFile
debug streamer output file used if mDebug is set
float maxSubthresholdChargeTot
upper limit for the per-region minimum qTot used as the virtual charge of a subthreshold cluster (def...
ClusterFlags clusterMask
clusters to exclude
unsigned short stackBoundaryMethod
stack boundary cluster exclusion method
float high
higher cluster cut
float low
lower cluster cut
unsigned short sameRowClusterMethod
same (sector,row) cluster group combination method: 0 = do not merge (one sample per cluster),...
constexpr size_t min
std::vector< Cluster > clusters
std::vector< int > row