1616
1717#ifndef GPUCA_GPUCODE
1818
19+ #include < algorithm>
1920#include < array>
2021#include < cmath>
22+ #include < limits>
2123
2224#include < gsl/span>
2325
@@ -36,6 +38,84 @@ namespace o2::itsmft::tracking
3638namespace detail
3739{
3840
41+ constexpr float MinCircleFitBz = 0 .01f ; // kG
42+
43+ struct CircleFitPoint {
44+ double x, y;
45+ double xx, xy, yy;
46+ };
47+
48+ // Fit y = a + b*x + c*(x*x + y*y) after translating, rotating and scaling
49+ // the attached hits. Iteratively project their xy covariance onto the circle
50+ // normal. Double precision is confined to this weak-bending seed estimate.
51+ inline double estimateCircleQOverPt (gsl::span<const CircleFitPoint> points, double bz) noexcept
52+ {
53+ constexpr double invalid = std::numeric_limits<double >::quiet_NaN ();
54+ if (points.size () < 3 || !std::isfinite (bz) || std::abs (bz) < MinCircleFitBz) {
55+ return invalid;
56+ }
57+ const double x0 = points.front ().x , y0 = points.front ().y ;
58+ const double dx = points.back ().x - x0, dy = points.back ().y - y0;
59+ const double length = std::hypot (dx, dy);
60+ if (!(length > 0 .) || !std::isfinite (length)) {
61+ return invalid;
62+ }
63+ const double cs = dx / length, sn = dy / length;
64+ std::array<double , 3 > fit{};
65+ for (int iteration = 0 ; iteration < 4 ; ++iteration) {
66+ double matrix[3 ][4 ]{};
67+ for (const auto & point : points) {
68+ const double x = ((point.x - x0) * cs + (point.y - y0) * sn) / length;
69+ const double y = (-(point.x - x0) * sn + (point.y - y0) * cs) / length;
70+ const double nx = -fit[1 ] - 2 * fit[2 ] * x, ny = 1 - 2 * fit[2 ] * y;
71+ const double gx = cs * nx - sn * ny, gy = sn * nx + cs * ny;
72+ const double variance = (gx * gx * point.xx + 2 * gx * gy * point.xy + gy * gy * point.yy ) / (length * length);
73+ if (!(variance > 0 .) || !std::isfinite (variance)) {
74+ return invalid;
75+ }
76+ const double weight = 1 / variance, basis[3 ] = {1 , x, x * x + y * y};
77+ for (int i = 0 ; i < 3 ; ++i) {
78+ for (int j = 0 ; j < 3 ; ++j) {
79+ matrix[i][j] += weight * basis[i] * basis[j];
80+ }
81+ matrix[i][3 ] += weight * basis[i] * y;
82+ }
83+ }
84+ // Solve the three normal equations with partial pivoting.
85+ for (int i = 0 ; i < 3 ; ++i) {
86+ int pivot = i;
87+ for (int j = i + 1 ; j < 3 ; ++j) {
88+ if (std::abs (matrix[j][i]) > std::abs (matrix[pivot][i])) {
89+ pivot = j;
90+ }
91+ }
92+ for (int k = i; k < 4 ; ++k) {
93+ std::swap (matrix[i][k], matrix[pivot][k]);
94+ }
95+ const double diagonal = matrix[i][i];
96+ if (std::abs (diagonal) < 1 .e -15 ) {
97+ return invalid;
98+ }
99+ for (int k = i; k < 4 ; ++k) {
100+ matrix[i][k] /= diagonal;
101+ }
102+ for (int j = 0 ; j < 3 ; ++j) {
103+ if (j != i) {
104+ const double factor = matrix[j][i];
105+ for (int k = i; k < 4 ; ++k) {
106+ matrix[j][k] -= factor * matrix[i][k];
107+ }
108+ }
109+ }
110+ }
111+ for (int i = 0 ; i < 3 ; ++i) {
112+ fit[i] = matrix[i][3 ];
113+ }
114+ }
115+ const double discriminant = 1 + fit[1 ] * fit[1 ] - 4 * fit[0 ] * fit[2 ];
116+ return discriminant > 0 ? 2 * fit[2 ] / (length * std::sqrt (discriminant) * bz * o2::constants::math::B2C ) : invalid;
117+ }
118+
39119struct RefitMeasurementSlot {
40120 SurfaceMeasurement measurement{};
41121 LayerId surface{};
@@ -117,27 +197,28 @@ inline bool driveRefitLeg(SurfaceTrackState& state, SurfaceTrackParameters& linR
117197
118198} // namespace detail
119199
120- // Reset a refit leg to a loose diagonal covariance .
200+ // Common first-pass prior for the two position coordinates, direction and q/pT .
121201GPUhdi () void resetCovarianceForRefit (SurfaceTrackState& state) noexcept
122202{
123203 for (auto & element : state.covariance ) {
124204 element = 0 .f ;
125205 }
126- if (state.kind == SurfaceKind::Cylinder) {
127- state.covariance [packedCovarianceIndex (0 , 0 )] = o2::track::kCY2max ;
128- state.covariance [packedCovarianceIndex (1 , 1 )] = o2::track::kCZ2max ;
129- state.covariance [packedCovarianceIndex (2 , 2 )] = o2::track::kCSnp2max ;
130- state.covariance [packedCovarianceIndex (3 , 3 )] = o2::track::kCTgl2max ;
131- const float q2pt = state.parameters [4 ];
132- state.covariance [packedCovarianceIndex (4 , 4 )] = q2pt * q2pt * o2::track::kC1Pt2max ;
133- } else {
134- constexpr float kCPhi2maxForward = o2::constants::math::PI * o2::constants::math::PI ;
135- state.covariance [packedCovarianceIndex (0 , 0 )] = o2::track::kCY2max ;
136- state.covariance [packedCovarianceIndex (1 , 1 )] = o2::track::kCY2max ;
137- state.covariance [packedCovarianceIndex (2 , 2 )] = kCPhi2maxForward ;
138- state.covariance [packedCovarianceIndex (3 , 3 )] = o2::track::kCTgl2max ;
139- const float invQPt = state.parameters [4 ];
140- state.covariance [packedCovarianceIndex (4 , 4 )] = invQPt * invQPt * o2::track::kC1Pt2max ;
206+ for (int i = 0 ; i < 4 ; ++i) {
207+ state.covariance [packedCovarianceIndex (i, i)] = 1 .f ;
208+ }
209+ // This is the variance, not the standard deviation.
210+ state.covariance [packedCovarianceIndex (4 , 4 )] = std::clamp (std::abs (state.parameters [4 ]), 1 .f , 10 .f );
211+ }
212+
213+ // Start a subsequent leg with five times the previous parameter uncertainties.
214+ GPUhdi () void inflateDiagonalCovarianceForRefit (SurfaceTrackState& state) noexcept
215+ {
216+ constexpr float varianceInflation = 25 .f ;
217+ for (int i = 0 ; i < 5 ; ++i) {
218+ for (int j = 0 ; j < i; ++j) {
219+ state.covariance [packedCovarianceIndex (i, j)] = 0 .f ;
220+ }
221+ state.covariance [packedCovarianceIndex (i, i)] *= varianceInflation;
141222 }
142223}
143224
@@ -187,6 +268,30 @@ inline bool fitTrackSeedLegs(
187268
188269 // Leg A: inward.
189270 SurfaceTrackState stateA = seed.state ();
271+ if (!std::isfinite (bz)) {
272+ return false ;
273+ }
274+ // There is no curvature constraint with the field off; keep the CA seed.
275+ if (std::abs (bz) >= detail::MinCircleFitBz) {
276+ std::array<detail::CircleFitPoint, MaxLayoutSurfaces> points{};
277+ std::size_t nPoints = 0 ;
278+ for (int layer = 0 ; layer < static_cast <int >(layerGlobals.size ()); ++layer) {
279+ const int cluster = seed.getCluster (layer);
280+ if (cluster == o2::its::constants::UnusedIndex) {
281+ continue ;
282+ }
283+ if (cluster < 0 || static_cast <std::size_t >(cluster) >= layerGlobals[layer].size ()) {
284+ return false ;
285+ }
286+ const auto & global = layerGlobals[layer][cluster];
287+ points[nPoints++] = {global.x , global.y , global.covariance .xx , global.covariance .xy , global.covariance .yy };
288+ }
289+ const float qOverPt = detail::estimateCircleQOverPt ({points.data (), nPoints}, bz);
290+ if (!std::isfinite (qOverPt)) {
291+ return false ;
292+ }
293+ stateA.parameters [4 ] = qOverPt;
294+ }
190295 SurfaceTrackParameters linRefA{stateA};
191296 resetCovarianceForRefit (stateA);
192297 float chi2A = 0 .f ;
@@ -209,7 +314,7 @@ inline bool fitTrackSeedLegs(
209314 // Leg B: outward; this is the reported inner result.
210315 SurfaceTrackState stateB = stateA;
211316 SurfaceTrackParameters linRefB{stateB};
212- resetCovarianceForRefit (stateB);
317+ inflateDiagonalCovarianceForRefit (stateB);
213318 float chi2B = 0 .f ;
214319 uint32_t acceptedB = 0 ;
215320 const auto slotsB = detail::assembleRefitLegSlots (seed, frame, layerGlobals, activeSurfaceCount - 1 , -1 , -1 , activeSlots, validSlots);
@@ -240,7 +345,7 @@ inline bool fitTrackSeedLegs(
240345 if (repeatRefitOut) {
241346 SurfaceTrackState stateC = stateB;
242347 SurfaceTrackParameters linRefC{stateC};
243- resetCovarianceForRefit (stateC);
348+ inflateDiagonalCovarianceForRefit (stateC);
244349 float chi2C = 0 .f ;
245350 uint32_t acceptedC = 0 ;
246351 const auto slotsC = detail::assembleRefitLegSlots (seed, frame, layerGlobals, 0 , activeSurfaceCount, 1 , activeSlots, validSlots);
0 commit comments