TPC: Improve standalone dEdx calculation class - #15650
tubagundem wants to merge 8 commits into
Conversation
|
Is this relevant for the pb26 production? |
No, this is just to improve the dE/dx calculation class if we want to use it later or if we want to debug things locally. |
…n calibration when isMC is set, remove MC kinematics reader from dEdx calculation macro
…ck without repeating track propagation and introduced minor improvements
… Nico's changes to dEdx calcualtion class, minor improvements
… style outer-row cut, align correction order and sin^2(phi) cap with online GPUdEdx
…ference()/GPUTrackingRefit::RefitTrack() report whether they reached the reference X) so CalculatedEdx recovers rows where RefitTrackAsGPU() stalls, redesign same-row cluster handling into a configurable dEdxSettings::sameRowClusterMethod with a pad/time proximity gate, remove the duplicated legacy calculatedEdx() implementation in favor of the shared two-pass gatherRowClusterData()/calculatedEdxFromRowData() path, and simplify calculatedEdx.C into a single-threaded usage example
|
Error while checking build/O2/fullCI_slc9 for d990619 at 2026-09-19 02:19: Full log here. |
| if (param.rec.tpc.trackReferenceX <= 500) { | ||
| GPUTPCGMTrackParam save = *this; | ||
| float saveAlpha = Alpha; | ||
| reachedReference = false; |
There was a problem hiding this comment.
Why is this needed? Just return false if the loop is completed w/o returning true.
| clusterState = 0; | ||
| while (true) { | ||
| const ClusterNative& GPUrestrict() cl = merger->GetConstantMem()->ioPtrs.clustersNative->clustersLinear[clusters[ihit].num]; | ||
| const ClusterNative& GPUrestrict() cl = merger -> GetConstantMem()->ioPtrs.clustersNative->clustersLinear[clusters[ihit].num]; |
There was a problem hiding this comment.
Not sure this r formatting is correct. Also, it is quite sporadic.Was it imposed by the clang-format?
There was a problem hiding this comment.
This is a bug in clang-format. You can leave it in or not, it will not break the code. But I will most probably revert such bogus formatting with my next PR.
| public: | ||
| // The driver must call clear(), fill clusters row by row outside-in, then run computedEdx() to get the result | ||
| GPUd() void clear(); | ||
| GPUd() void fillCluster(float qtot, float qmax, int32_t padRow, uint8_t sector, float trackSnp, float trackTgl, const GPUCalibObjectsConst& calib, float z, float pad, float relTime); |
There was a problem hiding this comment.
Please do not unconditionally add parameters only for the debug streamers.
Please use GPUCA_DEBUG_STREAMER_CHECK(, int32_t iTrk, uint8_t flags).
| clusterState = 0; | ||
| while (true) { | ||
| const ClusterNative& GPUrestrict() cl = merger->GetConstantMem()->ioPtrs.clustersNative->clustersLinear[clusters[ihit].num]; | ||
| const ClusterNative& GPUrestrict() cl = merger -> GetConstantMem()->ioPtrs.clustersNative->clustersLinear[clusters[ihit].num]; |
There was a problem hiding this comment.
This is a bug in clang-format. You can leave it in or not, it will not break the code. But I will most probably revert such bogus formatting with my next PR.
| GPUO2InterfaceRefit(const o2::tpc::ClusterNativeAccess* cl, const o2::gpu::TPCFastTransformPOD* trans, float bzNominalGPU, const o2::tpc::TPCClRefElem* trackRef, uint32_t nHbfPerTf = 0, const uint8_t* sharedmap = nullptr, const uint32_t* occupancymap = nullptr, int32_t occupancyMapSize = -1, const std::vector<o2::tpc::TrackTPC>* trks = nullptr, o2::base::Propagator* p = nullptr); | ||
| ~GPUO2InterfaceRefit(); | ||
|
|
||
| int32_t RefitTrackAsGPU(o2::tpc::TrackTPC& trk, bool outward = false, bool resetCov = false); |
There was a problem hiding this comment.
Do we really need this additional return value? Can't you just check at the end if the track x position is at the reference?
Changes
Merge same-row clusters (redesigned into a configurable, proximity-gated method), then fix the traversal bug the original merge exposed:
The first two commits add
handleSameRowClusters(), which unconditionally merged every cluster falling in the same (sector, row) and assigned to the same track into one combined cluster; pad/time a charge-weighted average, total charge summed, max charge the max across the group; and fixed a traversal bug this merge exposed by walking clusters in the track's actual native cluster-reference order (rowOrder, recorded on first encounter) instead of assuming a fixed row order. This was later redesigned: a same-row group is now only eligible for merging if it passes a pad/time proximity gate (setSameRowMaxPadDiff()/setSameRowMaxTimeDiff(), default 3 pad / 4 time bins); a group that fails it (e.g. the two legs of a low-p looper crossing the same row) is always kept as one independent sample per cluster, since merging those was biasing the low-p OROC3 dE/dx tail. For groups that do pass the gate, the newdEdxSettings::sameRowClusterMethodselects how:0= don't merge (one sample per raw cluster),1= merge and sum (the original charge-weighted-average/sum behavior),2(default) = merge by taking the single dominant (largest qTot) fragment.Fixed frequent track-propagation failures, plus a faster propagation option:
Track propagation (
mPropagateTrack) previously left the track state corrupted ifrotate()orPropagateToXBxByBzfailed partway through a step, causing every later row's propagation to fail too. Both now snapshot the track state first and roll back to it on failure, so one bad row no longer poisons the rest. Also addedsetPropagateParams()/mPropagateParams, a cheaper alternative that propagates only the track parameters (no material corrections) instead of the full track.Added a GPU-refit-to-propagation fallback for stalled refits:
GPUTPCGMTrackParam::MoveToReference()andGPUTrackingRefit::RefitTrack()(and itsRefitTrackAsGPU/RefitTrackAsTrackParCovoverloads) now report through an outputreachedReferenceOutflag whether the fit actually reached the requested reference X, instead of silently leaving the track at a stale/wrong position when it couldn't; this mainly affected low p tracks whose fitted helix falls short of an outer row.CalculatedEdx's refit path uses this: the first time a track's refit fails to reach a row, it permanently switches that track to plain propagation (newpropagateTrackToX(), the same fallback ladder asmPropagateTrack/mPropagateParams) chained from its pristine pre-refit seed, instead of continuing to call a refit that will keep failing on later rows too.Excluded clusters no longer bias the subthreshold fill:
Minimum-charge tracking and missing-cluster counting used to run over every cluster, including ones that would later be excluded (dead region, edge, failed propagation, etc.). Now they only run over clusters that are actually accepted into the dE/dx calculation.
New cluster-exclusion:
ExcludeSplitClsplit intoExcludeSplitPadClandExcludeSplitTimeClfor finer-grained control (bit values renumbered).ExcludeSamePadRowCl— exclude clusters that were merged from the same pad row.isInStackBoundaries()and amStackBoundariestable (row ranges per GEM stack), controlled by a newstackBoundaryMethodparameter (0 = disabled, 1 = exclude boundary row, 2 = also exclude the adjacent row).loadCalibsFromCCDB()) and clusters in dead regions are excluded.Per-region occupancy output:
Added an
AverageOccupancystruct (IROC/OROC1-3) and a new output parameter oncalculatedEdx()returning average track occupancy per TPC region.Debug streamer overhaul:
Replaced the old per-track parallel debug vectors with two linked trees:
dEdxDebugTrack(one row per track: pristine track, output, average occupancy, summary counts) anddEdxDebugCl(one row per cluster, with the track's propagated/refit parameters at that cluster). Rows are matched via a new runningmDebugTrackIndex.setStreamer()also now keeps a map of streamers keyed by output filename, so differentcalculatedEdx()calls writing to different debug files each get their own independent tree.Simplified subthreshold cluster filling:
Removed the Landau-based method (method=2) from
fillMissingClusters(), keeping only minimum-charge and minimum-charge/2.getOccupancy()signature change:Now takes a cluster time (
float) instead of aClusterNative, and is only valid when the refit method (not propagation) is used, since the occupancy map is only filled bysetRefit().Evaluate multiple dE/dx settings per track without repeating propagation:
New
dEdxSettingsstruct bundles a full configuration (truncation range, correction mask, cluster mask, subthreshold/stack-boundary/same-row-cluster method, per-call debug file, subthreshold charge caps). Per-track work is split intogatherRowClusterData()(refit/propagation, run once per track) andcalculatedEdxFromRowData()(settings-dependent charge computation, run once perdEdxSettingsentry against the shared row data). NewcalculatedEdxMultipleSettings()entry point ties the two together and returns onedEdxInfoper settings entry.Legacy
calculatedEdx()no longer duplicates the implementation:The standalone single-settings
calculatedEdx()overloads are now thin wrappers that build adEdxSettingsand delegate to the sharedgatherRowClusterData()/calculatedEdxFromRowData()path used bycalculatedEdxMultipleSettings().Subthreshold charge cap reintroduced as a per-region value clamp instead of an all-or-nothing gate:
The old
mMinChargeTotThreshold/mMinChargeMaxThresholdgated whether a gap got filled at all, track-wide across all regions: if the track's weakest accepted cluster anywhere exceeded the threshold, no gap on that track was filled in any region, undercounting subthreshold clusters relative to online tracking. Removed those members entirely; subthreshold gaps are now always filled (unless excluded), using a per-region running minimum charge, optionally clamped per settings entry via newdEdxSettings::maxSubthresholdChargeTot/maxSubthresholdChargeMax(default effectively uncapped) applied only to the fill value, never to whether/how many gaps get filled.fillMissingClusters()now takes per-region (float[4]) minimum-charge arrays instead of one track-wide scalar.Fixed a correction-map crash/inaccuracy from mutating an already-flattened transform in place:
CalculatedEdxbuilt its refit correction map as aTPCFastTransformPODand then mutated that same flattened buffer directly (setTPCCorrMap()/vDrift updates); not a supported use of the POD form, and a source of heap corruption under production load. Fixed by keeping a regular, exclusively-ownedTPCFastTransform(mTPCCorrMapFull) as the only object ever mutated, and re-flattening it into the POD buffer the GPU refitter reads via a newrebuildTPCCorrMapPOD()after every change. Along the way,loadCalibsFromCCDB()(and the matching local-snapshot loader) gained aloadVDriftForRefitoption (default on) that fetchesTPC/Calib/VDriftTgland applies it to the refit transform via newsetTPCVDrift()/setVDriftFromFile(); previously the refit transform carried no calibrated drift velocity/t0 at all, biasing the local z coordinates the refit sees.New debug/monitoring counters:
getNPropagationFailed()/getNRowsProcessed()track refit/propagation failure rate.getNRefitFallback()tracks how often a row recovered via the new refit-to-propagation fallback.getNSubThresholdFilledPerSettings()tracks subthreshold fills perdEdxSettingsentry.resetDebugCounters()resets all of them.Added
macro/calculatedEdx.C:Example macro demonstrating standalone use of
CalculatedEdx: reads TPC tracks and native clusters (data or MC), optionally restricting to TPC tracks matched to an ITS track, driven by a singlesettingsListvector parameter so multiple dE/dx settings can be evaluated per track in one run. The output tree gains onedEdx<i>branch persettingsListentry (instead of a singledEdxbranch). Processes each event's tracks sequentially through a singleCalculatedEdxinstance — an earlier version ran one instance per worker thread for parallel processing, butmPropagateTrackwas observed to give different results depending onnThreads, so multithreading was removed to keep this a reliable, simple usage example rather than a thread-count-dependent one (root cause of that discrepancy not yet identified); debug streamer files are now named per-setting only (dEdxDebug_s<j>.root) when more than one setting is requested, instead of per-thread-per-setting. Also adds amaxEventsparameter to cap the number of processed events, options to feed space-charge/drift-velocity calibration into the refit transform, and per-event logging of the refit/propagation failure rate and subthreshold-fill counts broken down per settings entry.