Project
Loading...
Searching...
No Matches
RefitDriver.h
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
12#ifndef ALICEO2_ITSMFT_TRACKING_REFITDRIVER_H_
13#define ALICEO2_ITSMFT_TRACKING_REFITDRIVER_H_
14
15#include "GPUCommonDef.h"
16
17#ifndef GPUCA_GPUCODE
18
19#include <algorithm>
20#include <array>
21#include <cmath>
22#include <limits>
23
24#include <gsl/span>
25
33
34// Descriptor-driven refit built on Propagator operations.
36{
37
38namespace detail
39{
40
41constexpr float MinCircleFitBz = 0.01f; // kG
42
44 float x, y;
45 float xx, xy, yy;
46};
47
48// Preserve cancellation in a*b-c*d with two fused multiply-add operations.
49inline float circleDifferenceOfProducts(float a, float b, float c, float d)
50{
51 const float cd = c * d;
52 return std::fma(a, b, -cd) + std::fma(-c, d, cd);
53}
54
56 float hi, lo;
57};
58
59// Return the rounded difference and its residual; do not reassociate these sums.
61{
62 const float hi = a - b;
63 const float bv = a - hi;
64 return {hi, (a - (hi + bv)) + (bv - b)};
65}
66
67// Fit y = a + b*x + c*(x*x + y*y) in a frame centered on the chord.
68// Compensate coordinate differences and the chord determinant to preserve
69// the small sagitta in float. Cache invariant transforms for the four
70// covariance-reweighting iterations; all fit arithmetic is single precision.
71inline float estimateCircleQOverPt(gsl::span<const CircleFitPoint> points, float bz) noexcept
72{
73 const float invalid = std::numeric_limits<float>::quiet_NaN();
74 if (points.size() < 3 || points.size() > MaxLayoutSurfaces || std::abs(bz) < MinCircleFitBz) {
75 return invalid;
76 }
77 const float x0 = points.front().x, y0 = points.front().y;
78 const auto dx = circleTwoDiff(points.back().x, x0);
79 const auto dy = circleTwoDiff(points.back().y, y0);
80 float lengthSquared = std::fma(dx.hi, dx.hi, dy.hi * dy.hi);
81 lengthSquared += 2.f * std::fma(dx.hi, dx.lo, dy.hi * dy.lo);
82 const float length = std::sqrt(lengthSquared);
83 if (!(length > 0.f) || !std::isfinite(length)) {
84 return invalid;
85 }
86 const float cs = dx.hi / length, sn = dy.hi / length;
87 const float invLengthSquared = 1.f / lengthSquared;
88 struct CachedPoint {
89 float x, y, r2, xx, xy, yy;
90 };
91 std::array<CachedPoint, MaxLayoutSurfaces> cache;
92 for (std::size_t i = 0; i < points.size(); ++i) {
93 const auto& in = points[i];
94 const auto px = circleTwoDiff(in.x, x0);
95 const auto py = circleTwoDiff(in.y, y0);
96 // Retain subtraction residuals before dividing the small determinant.
97 float cross = circleDifferenceOfProducts(dx.hi, py.hi, dy.hi, px.hi);
98 float dot = std::fma(dx.hi, px.hi, dy.hi * py.hi);
99
100 float correction = std::fma(dx.hi, py.lo, dx.lo * py.hi);
101 correction = std::fma(-dy.hi, px.lo, correction);
102 correction = std::fma(-dy.lo, px.hi, correction);
103 correction += circleDifferenceOfProducts(dx.lo, py.lo, dy.lo, px.lo);
104 cross += correction;
105 dot += std::fma(dx.hi, px.lo, std::fma(dx.lo, px.hi, std::fma(dy.hi, py.lo, dy.lo * py.hi)));
106
107 const float x = dot * invLengthSquared - .5f;
108 const float y = cross * invLengthSquared;
109 const float xx = in.xx, xy = in.xy, yy = in.yy;
110 cache[i] = {x, y, std::fma(x, x, y * y),
111 std::fma(cs * cs, xx, std::fma(2.f * cs * sn, xy, sn * sn * yy)) * invLengthSquared,
112 std::fma(-cs * sn, xx, std::fma(std::fma(cs, cs, -sn * sn), xy, cs * sn * yy)) * invLengthSquared,
113 std::fma(sn * sn, xx, std::fma(-2.f * cs * sn, xy, cs * cs * yy)) * invLengthSquared};
114 }
115 std::array<float, 3> fit{};
116 for (int iteration = 0; iteration < 4; ++iteration) {
117 float matrix[3][4]{};
118 for (const auto& point : gsl::span<const CachedPoint>{cache.data(), points.size()}) {
119 const float nx = std::fma(-2.f * fit[2], point.x, -fit[1]);
120 const float ny = std::fma(-2.f * fit[2], point.y, 1.f);
121 const float variance = std::fma(nx * nx, point.xx, std::fma(2.f * nx * ny, point.xy, ny * ny * point.yy));
122 if (!(variance > 0.f) || !std::isfinite(variance)) {
123 return invalid;
124 }
125
126 const float weight = 1.f / variance, basis[4] = {1.f, point.x, point.r2, point.y};
127 for (int i = 0; i < 3; ++i) {
128 const float weighted = weight * basis[i];
129 for (int j = i; j < 4; ++j) {
130 matrix[i][j] = std::fma(weighted, basis[j], matrix[i][j]);
131 }
132 }
133 }
134
135 matrix[1][0] = matrix[0][1];
136 matrix[2][0] = matrix[0][2];
137 matrix[2][1] = matrix[1][2];
138 // Solve the three normal equations with partial pivoting.
139 for (int i = 0; i < 3; ++i) {
140 int pivot = i;
141 for (int j = i + 1; j < 3; ++j) {
142 if (std::abs(matrix[j][i]) > std::abs(matrix[pivot][i])) {
143 pivot = j;
144 }
145 }
146 for (int k = i; k < 4; ++k) {
147 std::swap(matrix[i][k], matrix[pivot][k]);
148 }
149 const float diagonal = matrix[i][i];
150 if (std::abs(diagonal) < 1.e-15f) {
151 return invalid;
152 }
153 for (int k = i; k < 4; ++k) {
154 matrix[i][k] /= diagonal;
155 }
156 for (int j = 0; j < 3; ++j) {
157 if (j == i) {
158 continue;
159 }
160 const float factor = matrix[j][i];
161 for (int k = i; k < 4; ++k) {
162 matrix[j][k] = std::fma(-factor, matrix[i][k], matrix[j][k]);
163 }
164 }
165 }
166 for (int i = 0; i < 3; ++i) {
167 fit[i] = matrix[i][3];
168 }
169 }
170 const float discriminant = std::fma(-4.f * fit[0], fit[2], std::fma(fit[1], fit[1], 1.f));
171 return discriminant > 0.f ? 2.f * fit[2] / (length * std::sqrt(discriminant) * bz * o2::constants::math::B2C) : invalid;
172}
173
179
181inline gsl::span<const RefitMeasurementSlot> assembleRefitLegSlots(
182 const TrackSeed& seed,
183 const TimeFrame& frame,
184 gsl::span<const gsl::span<const GlobalMeasurement>> layerGlobals,
185 int start, int end, int step,
186 gsl::span<RefitMeasurementSlot> out,
187 bool& valid) noexcept
188{
189 valid = layerGlobals.size() <= MaxLayoutSurfaces;
190 int position = 0;
191 for (int surfacePosition = start; surfacePosition != end && position < static_cast<int>(out.size()); surfacePosition += step) {
192 const int clsIdx = seed.getCluster(surfacePosition);
193 if (clsIdx == o2::its::constants::UnusedIndex) {
194 out[position++] = {};
195 continue;
196 }
197 if (!valid || clsIdx < 0 || static_cast<std::size_t>(clsIdx) >= layerGlobals[surfacePosition].size()) {
198 valid = false;
199 return {};
200 }
201 const auto& global = layerGlobals[surfacePosition][clsIdx];
202 const auto surface = LayerId{static_cast<uint16_t>(surfacePosition)};
203 const auto* measurement = frame.getSurfaceMeasurement(surface, global.clusterId);
204 if (measurement == nullptr) {
205 valid = false;
206 return {};
207 }
208 out[position++] = RefitMeasurementSlot{*measurement, surface, true};
209 }
210 return gsl::span<const RefitMeasurementSlot>(out.data(), position);
211}
212
213// Holes are skipped; present slots must resolve to a descriptor. Commit state,
214// reference, chi2 and count only after the full leg succeeds.
216 float& chi2, uint32_t& acceptedHitCount,
217 gsl::span<const RefitMeasurementSlot> orderedSlots, SurfaceCatalogView surfaceCatalog,
218 float bz, material::MaterialTraversalDirection direction,
219 bool shiftReferenceToMeasurement, float maxChi2) noexcept
220{
221 if (chi2 < 0.f) {
222 return false;
223 }
224
225 SurfaceTrackState scratchState = state;
226 SurfaceTrackParameters scratchLinRef = linRef;
227 float scratchChi2 = chi2;
228 uint32_t scratchAcceptedHitCount = 0;
229 constexpr uint32_t kChi2GateMinAcceptedHits = 3;
230 for (const auto& slot : orderedSlots) {
231 if (!slot.present) {
232 continue;
233 }
234 if (!slot.surface.isValid() || !(surfaceCatalog.nSurfaces == 0 || surfaceCatalog.surfaces != nullptr) ||
235 !(slot.surface.value() < surfaceCatalog.nSurfaces)) {
236 return false;
237 }
238 const SurfaceDescriptor& descriptor = surfaceCatalog.getSurface(slot.surface);
239 if (!Propagator::propagateToMeasurement(scratchState, scratchLinRef, descriptor, slot.measurement, bz, direction,
240 scratchAcceptedHitCount >= kChi2GateMinAcceptedHits, maxChi2, scratchChi2,
241 shiftReferenceToMeasurement)) {
242 return false;
243 }
244 ++scratchAcceptedHitCount;
245 }
246 state = scratchState;
247 linRef = scratchLinRef;
248 chi2 = scratchChi2;
249 acceptedHitCount = scratchAcceptedHitCount;
250 return true;
251}
252
253} // namespace detail
254
255// Common first-pass prior for the two position coordinates, direction and q/pT.
256GPUhdi() void resetCovarianceForRefit(SurfaceTrackState& state) noexcept
257{
258 for (auto& element : state.covariance) {
259 element = 0.f;
260 }
261 for (int i = 0; i < 4; ++i) {
262 state.covariance[packedCovarianceIndex(i, i)] = 1.f;
263 }
264 // This is the variance, not the standard deviation.
265 state.covariance[packedCovarianceIndex(4, 4)] = std::clamp(std::abs(state.parameters[4]), 1.f, 10.f);
266}
267
268// Start a subsequent leg with five times the previous parameter uncertainties.
269GPUhdi() void inflateDiagonalCovarianceForRefit(SurfaceTrackState& state) noexcept
270{
271 constexpr float varianceInflation = 25.f;
272 for (int i = 0; i < 5; ++i) {
273 for (int j = 0; j < i; ++j) {
274 state.covariance[packedCovarianceIndex(i, j)] = 0.f;
275 }
276 state.covariance[packedCovarianceIndex(i, i)] *= varianceInflation;
277 }
278}
279
280// parameters[4] is signed q/pT for both coordinate conventions.
281GPUhdi() float ptFromQOverPt(float q2pt, uint8_t absCharge) noexcept
282{
283 float ptInv = std::abs(q2pt);
284 if (ptInv < o2::track::MinPTInv) {
285 ptInv = o2::track::MinPTInv;
286 }
287 if (absCharge > 1) {
288 ptInv /= static_cast<float>(absCharge);
289 }
290 return 1.f / ptInv;
291}
292
293// Refit inward, outward, then optionally inward again; commit on success.
295 const TrackSeed& seed,
296 const TimeFrame& frame,
297 gsl::span<const gsl::span<const GlobalMeasurement>> layerGlobals,
298 SurfaceCatalogView surfaceCatalog,
299 float bz,
300 bool shiftReferenceToMeasurement,
301 float maxChi2ClusterAttachment,
302 float maxChi2NDF,
303 bool repeatRefitOut,
304 gsl::span<const float> minPt,
305 SurfaceTrackState& outParamIn,
306 SurfaceTrackState& outParamOut,
307 float& outChi2) noexcept
308{
309 if (layerGlobals.empty() || layerGlobals.size() > MaxLayoutSurfaces) {
310 return false;
311 }
312 // Legs run sequentially; reuse bounded storage without allocating inside
313 // this noexcept refit. Only the active portion is exposed to the assembler.
314 std::array<detail::RefitMeasurementSlot, MaxLayoutSurfaces> slotsBuffer{};
315 const gsl::span<detail::RefitMeasurementSlot> activeSlots{slotsBuffer.data(), layerGlobals.size()};
316 auto legAcceptable = [](const SurfaceTrackState& state, float chi2, uint32_t acceptedHitCount,
317 float maxQoverPt, float maxChi2NDFValue) noexcept -> bool {
318 if (!(std::abs(state.parameters[4]) < maxQoverPt)) {
319 return false;
320 }
321 return chi2 < maxChi2NDFValue * static_cast<float>(static_cast<int>(acceptedHitCount) * 2 - 5);
322 };
323
324 // Leg A: inward.
325 SurfaceTrackState stateA = seed.state();
326 if (!std::isfinite(bz)) {
327 return false;
328 }
329 // There is no curvature constraint with the field off; keep the CA seed.
330 if (std::abs(bz) >= detail::MinCircleFitBz) {
331 std::array<detail::CircleFitPoint, MaxLayoutSurfaces> points{};
332 std::size_t nPoints = 0;
333 for (int layer = 0; layer < static_cast<int>(layerGlobals.size()); ++layer) {
334 const int cluster = seed.getCluster(layer);
335 if (cluster == o2::its::constants::UnusedIndex) {
336 continue;
337 }
338 if (cluster < 0 || static_cast<std::size_t>(cluster) >= layerGlobals[layer].size()) {
339 return false;
340 }
341 const auto& global = layerGlobals[layer][cluster];
342 points[nPoints++] = {global.x, global.y, global.covariance.xx, global.covariance.xy, global.covariance.yy};
343 }
344 const float qOverPt = detail::estimateCircleQOverPt({points.data(), nPoints}, bz);
345 if (!std::isfinite(qOverPt)) {
346 return false;
347 }
348 stateA.parameters[4] = qOverPt;
349 }
350 SurfaceTrackParameters linRefA{stateA};
351 resetCovarianceForRefit(stateA);
352 float chi2A = 0.f;
353 uint32_t acceptedA = 0;
354 const int activeSurfaceCount = static_cast<int>(layerGlobals.size());
355 bool validSlots = false;
356 const auto slotsA = detail::assembleRefitLegSlots(seed, frame, layerGlobals, 0, activeSurfaceCount, 1, activeSlots, validSlots);
357 if (!validSlots) {
358 return false;
359 }
360 if (!detail::driveRefitLeg(stateA, linRefA, chi2A, acceptedA, slotsA, surfaceCatalog, bz,
361 material::MaterialTraversalDirection::AlongMomentum, shiftReferenceToMeasurement,
362 maxChi2ClusterAttachment)) {
363 return false;
364 }
365 if (!legAcceptable(stateA, chi2A, acceptedA, o2::constants::math::VeryBig, maxChi2NDF)) {
366 return false;
367 }
368
369 // Leg B: outward; this is the reported inner result.
370 SurfaceTrackState stateB = stateA;
371 SurfaceTrackParameters linRefB{stateB};
372 inflateDiagonalCovarianceForRefit(stateB);
373 float chi2B = 0.f;
374 uint32_t acceptedB = 0;
375 const auto slotsB = detail::assembleRefitLegSlots(seed, frame, layerGlobals, activeSurfaceCount - 1, -1, -1, activeSlots, validSlots);
376 if (!validSlots) {
377 return false;
378 }
379 if (!detail::driveRefitLeg(stateB, linRefB, chi2B, acceptedB, slotsB, surfaceCatalog, bz,
381 maxChi2ClusterAttachment)) {
382 return false;
383 }
384 if (!legAcceptable(stateB, chi2B, acceptedB, 50.f, maxChi2NDF)) {
385 return false;
386 }
387
388 // MinPt uses the seed's attached-cluster count.
389 const int nClAttached = seed.getHitLayerMask().count();
390 const int minPtSlot = activeSurfaceCount - nClAttached;
391 if (minPtSlot >= 0 && minPtSlot < static_cast<int>(minPt.size())) {
392 const float minPtThreshold = minPt[minPtSlot];
393 if (minPtThreshold > 0.f && ptFromQOverPt(stateB.parameters[4], stateB.absCharge) < minPtThreshold) {
394 return false;
395 }
396 }
397
398 // Optional leg C: inward again.
399 SurfaceTrackState stateOut = stateA;
400 if (repeatRefitOut) {
401 SurfaceTrackState stateC = stateB;
402 SurfaceTrackParameters linRefC{stateC};
403 inflateDiagonalCovarianceForRefit(stateC);
404 float chi2C = 0.f;
405 uint32_t acceptedC = 0;
406 const auto slotsC = detail::assembleRefitLegSlots(seed, frame, layerGlobals, 0, activeSurfaceCount, 1, activeSlots, validSlots);
407 if (!validSlots) {
408 return false;
409 }
410 if (!detail::driveRefitLeg(stateC, linRefC, chi2C, acceptedC, slotsC, surfaceCatalog, bz,
411 material::MaterialTraversalDirection::AlongMomentum, shiftReferenceToMeasurement,
412 maxChi2ClusterAttachment)) {
413 return false;
414 }
415 if (!legAcceptable(stateC, chi2C, acceptedC, o2::constants::math::VeryBig, maxChi2NDF)) {
416 return false;
417 }
418 stateOut = stateC;
419 }
420
421 outParamIn = stateB;
422 outParamOut = stateOut;
423 outChi2 = chi2B;
424 return true;
425}
426
427} // namespace o2::itsmft::tracking
428
429#endif // GPUCA_GPUCODE
430
431#endif /* ALICEO2_ITSMFT_TRACKING_REFITDRIVER_H_ */
Passive common TimeFrame owner.
atype::type element
int32_t i
SurfaceTrackState state
float chi2
useful math constants
bool valid
uint32_t j
Definition RawData.h:0
uint32_t c
Definition RawData.h:2
GPU-portable whole-track seed for common CA tracking.
GLint GLenum GLint x
Definition glcorearb.h:403
GLuint GLuint end
Definition glcorearb.h:469
GLuint GLuint GLfloat weight
Definition glcorearb.h:5477
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLint y
Definition glcorearb.h:270
GLuint GLsizei GLsizei * length
Definition glcorearb.h:790
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLuint GLfloat x0
Definition glcorearb.h:5034
GLenum GLuint GLint GLint layer
Definition glcorearb.h:1310
GLuint start
Definition glcorearb.h:469
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLuint GLfloat GLfloat y0
Definition glcorearb.h:5034
constexpr int UnusedIndex
Definition Constants.h:32
float estimateCircleQOverPt(gsl::span< const CircleFitPoint > points, float bz) noexcept
Definition RefitDriver.h:71
gsl::span< const RefitMeasurementSlot > assembleRefitLegSlots(const TrackSeed &seed, const TimeFrame &frame, gsl::span< const gsl::span< const GlobalMeasurement > > layerGlobals, int start, int end, int step, gsl::span< RefitMeasurementSlot > out, bool &valid) noexcept
Builds an ordered refit leg; holes remain explicit.
constexpr float MinCircleFitBz
Definition RefitDriver.h:41
CircleFloatDifference circleTwoDiff(float a, float b)
Definition RefitDriver.h:60
bool driveRefitLeg(SurfaceTrackState &state, SurfaceTrackParameters &linRef, float &chi2, uint32_t &acceptedHitCount, gsl::span< const RefitMeasurementSlot > orderedSlots, SurfaceCatalogView surfaceCatalog, float bz, material::MaterialTraversalDirection direction, bool shiftReferenceToMeasurement, float maxChi2) noexcept
float circleDifferenceOfProducts(float a, float b, float c, float d)
Definition RefitDriver.h:49
bool fitTrackSeedLegs(const TrackSeed &seed, const TimeFrame &frame, gsl::span< const gsl::span< const GlobalMeasurement > > layerGlobals, SurfaceCatalogView surfaceCatalog, float bz, bool shiftReferenceToMeasurement, float maxChi2ClusterAttachment, float maxChi2NDF, bool repeatRefitOut, gsl::span< const float > minPt, SurfaceTrackState &outParamIn, SurfaceTrackState &outParamOut, float &outChi2) noexcept
uint32_t trackClusterIndicesSize noexcept
GPUhdi() const expr bool isRecognizedSurfaceKind(SurfaceKind kind) noexcept
Definition IdTypes.h:65
constexpr uint32_t MaxLayoutSurfaces
Definition IdTypes.h:70