Project
Loading...
Searching...
No Matches
Propagator.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
13
14#include <cmath>
15#include <cstdint>
16#include <limits>
17
22
24{
25
26namespace
27{
28
29// Remove tiny negative diagonal values caused by floating-point cancellation
30// during covariance transport. Larger negative values remain errors.
31void clampNegligibleCovarianceNoise(SurfaceTrackState& state) noexcept
32{
33 constexpr float kNoiseFloor = 1.e-3f;
34 for (uint8_t i = 0; i < 5; ++i) {
35 const uint8_t index = packedCovarianceIndex(i, i);
36 if (state.covariance[index] < 0.f && state.covariance[index] > -kNoiseFloor) {
37 state.covariance[index] = 0.f;
38 }
39 }
40}
41
42// Apply outCov = J * inCov * J^T to a packed-symmetric 5x5 covariance.
43void congruenceTransform(const float (&inCov)[15], const float (&jacobian)[5][5], float (&outCov)[15]) noexcept
44{
45 float full[5][5];
46 for (uint8_t row = 0; row < 5; ++row) {
47 for (uint8_t col = 0; col < 5; ++col) {
48 full[row][col] = inCov[packedCovarianceIndex(row, col)];
49 }
50 }
51 float tmp[5][5];
52 for (uint8_t row = 0; row < 5; ++row) {
53 for (uint8_t col = 0; col < 5; ++col) {
54 float sum = 0.f;
55 for (uint8_t k = 0; k < 5; ++k) {
56 sum += jacobian[row][k] * full[k][col];
57 }
58 tmp[row][col] = sum;
59 }
60 }
61 for (uint8_t row = 0; row < 5; ++row) {
62 for (uint8_t col = 0; col <= row; ++col) {
63 float sum = 0.f;
64 for (uint8_t k = 0; k < 5; ++k) {
65 sum += tmp[row][k] * jacobian[col][k];
66 }
67 outCov[packedCovarianceIndex(row, col)] = sum;
68 }
69 }
70}
71
72// Convert Barrel (bY, bZ, Snp, Tgl, Q2Pt) to Forward
73// (X, Y, Phi, Tanl, InvQPt) on the fixed-z plane through the nominal point.
74bool barrelToForward(SurfaceTrackState& state, float bz) noexcept
75{
76 const float snp = state.parameters[2];
77 const float tanl = state.parameters[3];
78 if (!(std::abs(snp) < 1.f) || tanl == 0.f) {
79 return false;
80 }
81 const float csA = std::cos(state.alpha);
82 const float snA = std::sin(state.alpha);
83 const float csp = std::sqrt((1.f - snp) * (1.f + snp));
84 const float bX = state.referenceCoordinate;
85 const float bY = state.parameters[0];
86
87 const float xGlo = bX * csA - bY * snA;
88 const float yGlo = bX * snA + bY * csA;
89 const float zGlo = state.parameters[1];
90 float phi = std::remainder(state.alpha + std::asin(snp), o2::constants::math::TwoPI);
91 // Match the library's (-pi, pi] angle convention.
92 if (phi <= -o2::constants::math::PI) {
93 phi += o2::constants::math::TwoPI;
94 }
95
96 // A displaced source z reaches the fixed target plane after transverse
97 // path -deltaZ/tanl. Include both position and direction along that path.
98 const float curvature = state.parameters[4] * bz * o2::constants::math::B2C;
99 const float jacobian[5][5] = {
100 {-snA, -(csA * csp - snA * snp) / tanl, 0.f, 0.f, 0.f},
101 {csA, -(snA * csp + csA * snp) / tanl, 0.f, 0.f, 0.f},
102 {0.f, -curvature / tanl, 1.f / csp, 0.f, 0.f},
103 {0.f, 0.f, 0.f, 1.f, 0.f},
104 {0.f, 0.f, 0.f, 0.f, 1.f}};
105 float newCov[15];
106 congruenceTransform(state.covariance, jacobian, newCov);
107
108 const float newParameters[5] = {xGlo, yGlo, phi, state.parameters[3], state.parameters[4]};
109 for (uint8_t i = 0; i < 5; ++i) {
110 state.parameters[i] = newParameters[i];
111 }
112 for (uint8_t i = 0; i < 15; ++i) {
113 state.covariance[i] = newCov[i];
114 }
116 state.alpha = 0.f;
118 return true;
119}
120
121// Convert Forward (X, Y, Phi, Tanl, InvQPt) to Barrel
122// (bY, bZ, Snp, Tgl, Q2Pt) on the fixed local-x plane through the nominal
123// point. Both target alpha and local x are held fixed in the Jacobian.
124bool forwardToBarrel(SurfaceTrackState& state, float bz) noexcept
125{
126 const float x = state.parameters[0];
127 const float y = state.parameters[1];
128 const float r = std::sqrt(x * x + y * y);
129 if (!(r > 1.e-6f)) {
130 return false;
131 }
132 const float alpha = std::atan2(y, x);
133 const float csA = std::cos(alpha);
134 const float snA = std::sin(alpha);
135 const float phi = state.parameters[2];
136 const float csp = std::cos(phi - alpha);
137 const float snp = std::sin(phi - alpha);
138 // The barrel convention encodes only the positive-cosine branch at alpha.
139 // Reject inward/tangent directions rather than silently reversing them.
140 if (!(csp > 0.f && std::abs(snp) < 1.f)) {
141 return false;
142 }
143
144 const float bX = x * csA + y * snA;
145 const float bY = -x * snA + y * csA;
146 const float bZ = state.referenceCoordinate;
147
148 // A displacement along the plane normal shifts the intersection by
149 // transverse path -deltaX/csp, inducing local-y, z and direction errors.
150 const float curvature = state.parameters[4] * bz * o2::constants::math::B2C;
151 const float tanlOverCsp = state.parameters[3] / csp;
152 const float jacobian[5][5] = {
153 {-snA - snp * csA / csp, csA - snp * snA / csp, 0.f, 0.f, 0.f},
154 {-tanlOverCsp * csA, -tanlOverCsp * snA, 0.f, 0.f, 0.f},
155 {-curvature * csA, -curvature * snA, csp, 0.f, 0.f},
156 {0.f, 0.f, 0.f, 1.f, 0.f},
157 {0.f, 0.f, 0.f, 0.f, 1.f}};
158 float newCov[15];
159 congruenceTransform(state.covariance, jacobian, newCov);
160
161 const float newParameters[5] = {bY, bZ, snp, state.parameters[3], state.parameters[4]};
162 for (uint8_t i = 0; i < 5; ++i) {
163 state.parameters[i] = newParameters[i];
164 }
165 for (uint8_t i = 0; i < 15; ++i) {
166 state.covariance[i] = newCov[i];
167 }
169 state.alpha = alpha;
171 return true;
172}
173
174// Both attachment algorithms work on a candidate and commit only after every
175// fallible operation succeeds. Linearized attachment also keeps a local reference.
176struct AttachmentTransaction {
177 SurfaceTrackState state;
178 float chi2;
179
180 void commit(SurfaceTrackState& destination, float& destinationChi2) const noexcept
181 {
182 destination = state;
183 destinationChi2 = chi2;
184 }
185};
186
187bool acceptsAttachmentChi2(float predictedChi2, bool gateEnabled, float maxChi2) noexcept
188{
189 if (predictedChi2 < 0.f || (gateEnabled && predictedChi2 > maxChi2)) {
190 return false;
191 }
192 return true;
193}
194
195bool covarianceDiagonalsNonNegative(const SurfaceTrackState& state) noexcept
196{
197 for (uint8_t i = 0; i < 5; ++i) {
198 if (state.covariance[packedCovarianceIndex(i, i)] < 0.f) {
199 return false;
200 }
201 }
202 return true;
203}
204
205// Barrel covariance-range upper bound, in (Y, Z, Snp, Tgl, Q2Pt) slot order:
206// the retained TrackParametrizationWithError<float>::checkCovariance()
207// range-clamp values shared by material correction and barrel
208// propagation, rotation, and update sanitization.
209constexpr float kBarrelMaxDiagonal[5] = {o2::track::kCY2max, o2::track::kCZ2max, o2::track::kCSnp2max,
211
212using DenseMatrix5 = float[5][5];
213
214bool validateBarrelSource(const SurfaceTrackState& state) noexcept
215{
217 return false;
218 }
219 return true;
220}
221
222void unpackCovariance(const SurfaceTrackState& state, DenseMatrix5& covariance) noexcept
223{
224 for (uint8_t row = 0; row < 5; ++row) {
225 for (uint8_t column = 0; column < 5; ++column) {
226 covariance[row][column] = state.covariance[packedCovarianceIndex(row, column)];
227 }
228 }
229}
230
231void packCovariance(const DenseMatrix5& covariance, SurfaceTrackState& state) noexcept
232{
233 for (uint8_t row = 0; row < 5; ++row) {
234 for (uint8_t column = 0; column <= row; ++column) {
235 state.covariance[packedCovarianceIndex(row, column)] = covariance[row][column];
236 }
237 }
238}
239
240void identity(DenseMatrix5& matrix) noexcept
241{
242 for (uint8_t i = 0; i < 5; ++i) {
243 matrix[i][i] = 1.f;
244 }
245}
246
247void transportCovariance(SurfaceTrackState& state, const DenseMatrix5& jacobian) noexcept
248{
249 DenseMatrix5 covariance{};
250 DenseMatrix5 product{};
251 DenseMatrix5 transported{};
252 unpackCovariance(state, covariance);
253 for (uint8_t row = 0; row < 5; ++row) {
254 for (uint8_t column = 0; column < 5; ++column) {
255 for (uint8_t inner = 0; inner < 5; ++inner) {
256 product[row][column] += jacobian[row][inner] * covariance[inner][column];
257 }
258 }
259 }
260 for (uint8_t row = 0; row < 5; ++row) {
261 for (uint8_t column = 0; column < 5; ++column) {
262 for (uint8_t inner = 0; inner < 5; ++inner) {
263 transported[row][column] += product[row][inner] * jacobian[column][inner];
264 }
265 }
266 }
267 packCovariance(transported, state);
268}
269
270// Shared commit point for non-linRef rotate() and propagate(). It validates
271// and sanitizes the covariance on every exit, including dx == 0.
272bool commitBarrelPropagation(SurfaceTrackState& destination, SurfaceTrackState& scratch) noexcept
273{
274 sanitizeCovariance(scratch, kBarrelMaxDiagonal);
275 destination = scratch;
276 return true;
277}
278
279bool residualInverse(const SurfaceTrackState& state, const SurfaceMeasurement& measurement,
280 float& inverse00, float& inverse01, float& inverse11) noexcept
281{
282 if (!(measurement.covariance.uu >= 0.f) || !(measurement.covariance.vv >= 0.f)) {
283 return false;
284 }
285 const float s00 = state.covariance[packedCovarianceIndex(0, 0)] + measurement.covariance.uu;
286 const float s01 = state.covariance[packedCovarianceIndex(1, 0)] + measurement.covariance.uv;
287 const float s11 = state.covariance[packedCovarianceIndex(1, 1)] + measurement.covariance.vv;
288 const float determinant = s00 * s11 - s01 * s01;
289 if (determinant == 0.f) {
290 return false;
291 }
292 const float inverseDeterminant = 1.f / determinant;
293 inverse00 = s11 * inverseDeterminant;
294 inverse01 = -s01 * inverseDeterminant;
295 inverse11 = s00 * inverseDeterminant;
296 return true;
297}
298
299// Covariance-free propagation of SurfaceTrackParameters using the
300// TrackParametrization::propagateParamTo formula for charged particles.
301bool propagateReferenceParams(SurfaceTrackParameters& ref, float targetX, float bz) noexcept
302{
303 const float dx = targetX - ref.referenceCoordinate;
304 if (dx == 0.f) {
305 ref.referenceCoordinate = targetX;
306 return true;
307 }
308 const float snp = ref.parameters[2];
309 const float curvature = ref.parameters[4] * bz * o2::constants::math::B2C;
310 const float propagatedSnp = snp + curvature * dx;
311 if (std::abs(snp) >= 1.f || std::abs(propagatedSnp) >= 1.f) {
312 return false;
313 }
314 const float csp = std::sqrt((1.f - snp) * (1.f + snp));
315 const float propagatedCsp = std::sqrt((1.f - propagatedSnp) * (1.f + propagatedSnp));
316 if (csp == 0.f || propagatedCsp == 0.f) {
317 return false;
318 }
319 const float reciprocalCosines = 1.f / (csp + propagatedCsp);
320 const float dyOverDx = (snp + propagatedSnp) * reciprocalCosines;
321 const float x2r = curvature * dx;
322 const bool arcZ = std::abs(x2r) > 0.05f;
323 float dz = 0.f;
324 if (arcZ) {
325 const float argument = csp * propagatedSnp - propagatedCsp * snp;
326 if (std::abs(argument) > 1.f || curvature == 0.f) {
327 return false;
328 }
329 float angle = std::asin(argument);
330 if (snp * snp + propagatedSnp * propagatedSnp > 1.f && snp * propagatedSnp < 0.f) {
331 angle = propagatedSnp > 0.f ? o2::constants::math::PI - angle : -o2::constants::math::PI - angle;
332 }
333 dz = ref.parameters[3] / curvature * angle;
334 } else {
335 dz = dx * (propagatedCsp + propagatedSnp * dyOverDx) * ref.parameters[3];
336 }
337 ref.referenceCoordinate = targetX;
338 ref.parameters[0] += dx * dyOverDx;
339 ref.parameters[1] += dz;
340 ref.parameters[2] = propagatedSnp;
341 return true;
342}
343
344// Forward diagonals have no finite ceiling; non-negativity and correlations
345// are still checked.
346constexpr float kForwardNoRangeLimit = std::numeric_limits<float>::max();
347constexpr float kForwardMaxDiagonal[5] = {kForwardNoRangeLimit, kForwardNoRangeLimit, kForwardNoRangeLimit,
348 kForwardNoRangeLimit, kForwardNoRangeLimit};
349
350bool validateForwardSource(const SurfaceTrackState& state) noexcept
351{
353 return false;
354 }
355 return true;
356}
357
358// Sanitize covariance once, at the propagation commit point.
359bool commitPropagation(SurfaceTrackState& destination, SurfaceTrackState& scratch) noexcept
360{
361 sanitizeCovariance(scratch, kForwardMaxDiagonal);
362 destination = scratch;
363 return true;
364}
365
366bool propagateLinear(SurfaceTrackState& state, float targetZ) noexcept
367{
368 const float dz = targetZ - state.referenceCoordinate;
369 const float tanl = state.parameters[3];
370 if (tanl == 0.f && dz != 0.f) {
371 return false;
372 }
373 if (dz == 0.f) {
374 return true;
375 }
376 const float inverseTanl = 1.f / tanl;
377 const float n = dz * inverseTanl;
378 const float m = n * inverseTanl;
379 const float sinPhi = std::sin(state.parameters[2]);
380 const float cosPhi = std::cos(state.parameters[2]);
381 state.parameters[0] += n * cosPhi;
382 state.parameters[1] += n * sinPhi;
383 state.referenceCoordinate = targetZ;
384
385 DenseMatrix5 jacobian{};
386 identity(jacobian);
387 jacobian[0][2] = -n * sinPhi;
388 jacobian[0][3] = -m * cosPhi;
389 jacobian[1][2] = n * cosPhi;
390 jacobian[1][3] = -m * sinPhi;
391 transportCovariance(state, jacobian);
392 return true;
393}
394
395// Share the same helix and Jacobian between direct and reference propagation.
396// The midpoint-angle form avoids subtracting O(1/curvature) coordinates;
397// its sinc derivative also remains well conditioned for almost straight tracks.
398template <typename State>
399bool propagateHelixWithJacobian(State& state, float targetZ, float bz, DenseMatrix5& jacobian) noexcept
400{
401 identity(jacobian);
402 const float dz = targetZ - state.referenceCoordinate;
403 if (dz == 0.f) {
404 return true;
405 }
406 const float tanl = state.parameters[3];
407 const float inverseQPt = state.parameters[4];
408 if (tanl == 0.f || bz == 0.f || inverseQPt == 0.f) {
409 return false;
410 }
411 const float n = dz / tanl;
412 const float curvatureScale = -std::abs(o2::constants::math::B2C) * bz;
413 const float halfAnglePerQPt = 0.5f * curvatureScale * n;
414 const float halfAngle = inverseQPt * halfAnglePerQPt;
415 float sinc, sincDerivative;
416 if (std::abs(halfAngle) < 0.25f) {
417 // sin(h)/h and its derivative, including their limits at h = 0.
418 // Keep the cancellation-prone derivative quotient away from small h.
419 // At |h| <= 0.25 the omitted terms are below float precision.
420 const float h2 = halfAngle * halfAngle;
421 sinc = std::fma(h2, std::fma(h2, std::fma(h2, -1.f / 5040.f, 1.f / 120.f), -1.f / 6.f), 1.f);
422 sincDerivative = halfAngle * std::fma(h2, std::fma(h2, -1.f / 840.f, 1.f / 30.f), -1.f / 3.f);
423 } else {
424 sinc = std::sin(halfAngle) / halfAngle;
425 sincDerivative = (std::cos(halfAngle) - sinc) / halfAngle;
426 }
427 const float phi = state.parameters[2];
428 const float sinMid = std::sin(phi + halfAngle);
429 const float cosMid = std::cos(phi + halfAngle);
430 const float endPhi = phi + 2.f * halfAngle;
431 const float dx = n * sinc * cosMid;
432 const float dy = n * sinc * sinMid;
433
434 jacobian[0][2] = -dy;
435 jacobian[1][2] = dx;
436 jacobian[0][3] = -n / tanl * std::cos(endPhi);
437 jacobian[1][3] = -n / tanl * std::sin(endPhi);
438 jacobian[0][4] = n * halfAnglePerQPt * std::fma(sincDerivative, cosMid, -sinc * sinMid);
439 jacobian[1][4] = n * halfAnglePerQPt * std::fma(sincDerivative, sinMid, sinc * cosMid);
440 jacobian[2][3] = -2.f * halfAngle / tanl;
441 jacobian[2][4] = 2.f * halfAnglePerQPt;
442
443 state.parameters[0] = std::fma(n * sinc, cosMid, state.parameters[0]);
444 state.parameters[1] = std::fma(n * sinc, sinMid, state.parameters[1]);
445 state.parameters[2] = endPhi;
446 state.referenceCoordinate = targetZ;
447 return true;
448}
449
450bool propagateHelix(SurfaceTrackState& state, float targetZ, float bz) noexcept
451{
452 if (targetZ == state.referenceCoordinate) {
453 return true;
454 }
455 DenseMatrix5 jacobian{};
456 if (!propagateHelixWithJacobian(state, targetZ, bz, jacobian)) {
457 return false;
458 }
459 transportCovariance(state, jacobian);
460 return true;
461}
462
463bool propagateAccepted(SurfaceTrackState& destination, float targetZ, float bz) noexcept
464{
465 if (!validateForwardSource(destination)) {
466 return false;
467 }
468 SurfaceTrackState scratch = destination;
469 const bool success = std::abs(bz) > 0.01f ? propagateHelix(scratch, targetZ, bz)
470 : propagateLinear(scratch, targetZ);
471 return success && commitPropagation(destination, scratch);
472}
473
474// Reference-only position update with the Jacobian at the original parameters.
475bool referencePropagateLinear(SurfaceTrackParameters& ref, float targetZ, DenseMatrix5& jacobian) noexcept
476{
477 identity(jacobian);
478 const float dz = targetZ - ref.referenceCoordinate;
479 const float tanl = ref.parameters[3];
480 if (tanl == 0.f && dz != 0.f) {
481 return false;
482 }
483 if (dz == 0.f) {
484 return true;
485 }
486 const float inverseTanl = 1.f / tanl;
487 const float n = dz * inverseTanl;
488 const float m = n * inverseTanl;
489 const float sinPhi = std::sin(ref.parameters[2]);
490 const float cosPhi = std::cos(ref.parameters[2]);
491 ref.parameters[0] += n * cosPhi;
492 ref.parameters[1] += n * sinPhi;
493 ref.referenceCoordinate = targetZ;
494
495 jacobian[0][2] = -n * sinPhi;
496 jacobian[0][3] = -m * cosPhi;
497 jacobian[1][2] = n * cosPhi;
498 jacobian[1][3] = -m * sinPhi;
499 return true;
500}
501
502bool referencePropagateHelix(SurfaceTrackParameters& ref, float targetZ, float bz, DenseMatrix5& jacobian) noexcept
503{
504 return propagateHelixWithJacobian(ref, targetZ, bz, jacobian);
505}
506
507bool propagateAccepted(SurfaceTrackState& state, SurfaceTrackParameters& linRef, float targetZ, float bz) noexcept
508{
509 if (!validateForwardSource(state)) {
510 return false;
511 }
512 if (linRef.kind != SurfaceKind::Disk) {
513 return false;
514 }
515 // The fitted state and linearization reference must share the exact anchor;
516 // their parameters may differ. Forward alpha is always 0/unused.
517 if (state.referenceCoordinate != linRef.referenceCoordinate) {
518 return false;
519 }
520
521 SurfaceTrackParameters scratchRef = linRef;
522 DenseMatrix5 jacobian{};
523 const bool ok = std::abs(bz) > 0.01f ? referencePropagateHelix(scratchRef, targetZ, bz, jacobian)
524 : referencePropagateLinear(scratchRef, targetZ, jacobian);
525 if (!ok) {
526 return false;
527 }
528
529 float diff[5];
530 for (uint8_t i = 0; i < 5; ++i) {
531 diff[i] = state.parameters[i] - linRef.parameters[i];
532 }
533
534 SurfaceTrackState scratchState = state;
535 scratchState.referenceCoordinate = targetZ;
536 for (uint8_t row = 0; row < 5; ++row) {
537 float value = scratchRef.parameters[row];
538 for (uint8_t column = 0; column < 5; ++column) {
539 value += jacobian[row][column] * diff[column];
540 }
541 scratchState.parameters[row] = value;
542 }
543 transportCovariance(scratchState, jacobian);
544
545 // a large Jacobian step can break positive semidefiniteness via
546 // an off-diagonal term even when diagonals look valid. Sanitize before the
547 // next operation receives the covariance.
548 sanitizeCovariance(scratchState, kForwardMaxDiagonal);
549 state = scratchState;
550 linRef = scratchRef;
551 return true;
552}
553
554} // namespace
555
556// Work on copies so that any rejection leaves both the fitted state and its
557// incidence reference unchanged.
558bool Propagator::correctForMaterial(SurfaceTrackState& state, SurfaceTrackParameters& incidenceReference,
559 material::IntegratedMaterialBudget materialBudget,
560 material::MaterialTraversalDirection direction) noexcept
561{
562 if (state.parameters[4] == 0.f || incidenceReference.parameters[4] == 0.f) {
563 return false;
564 }
566 if (!(std::abs(state.parameters[2]) < 1.f) || !(std::abs(incidenceReference.parameters[2]) < 1.f)) {
567 return false;
568 }
569 } else if (state.parameters[3] == 0.f || incidenceReference.parameters[3] == 0.f) {
570 return false;
571 }
572 if (state.pid.getID() >= o2::track::PID::NIDsTot) {
573 return false;
574 }
575 if (state.pid.getMass() == 0.f) {
576 return false;
577 }
578 if (!covarianceDiagonalsNonNegative(state)) {
579 return false;
580 }
581
582 float momentumBeforeGeV = state.getP();
583 SurfaceTrackState scratchState = state;
584 SurfaceTrackParameters scratchReference = incidenceReference;
585 // Layer budgets describe normal incidence. Use the reference trajectory to
586 // scale both radiation length and areal density to the crossed path length.
587 const float tgl = scratchReference.parameters[3];
588 float incidenceScale;
590 const float snp = scratchReference.parameters[2];
591 const float cosPhi2 = (1.f - snp) * (1.f + snp);
592 const float inverseCosLambda2 = 1.f + tgl * tgl;
593 incidenceScale = std::sqrt(inverseCosLambda2 / cosPhi2);
594 } else {
595 incidenceScale = std::sqrt(1.f + tgl * tgl) / std::abs(tgl);
596 }
597 materialBudget.xOverX0 *= incidenceScale;
598 materialBudget.arealDensityGPerCm2 *= incidenceScale;
599 float momentumAfterGeV = 0.f;
600 float highlandTheta2Rad2 = 0.f;
601 float relativeInverseMomentumVariance = 0.f;
602 if (!material::calculateMaterialPhysics(momentumBeforeGeV, scratchState.pid, scratchState.absCharge, direction, materialBudget,
603 momentumAfterGeV, highlandTheta2Rad2, relativeInverseMomentumVariance)) {
604 return false;
605 }
606
607 // No material must also bypass covariance limiting.
608 const bool isNoopMaterial = (materialBudget.xOverX0 == 0.f && materialBudget.arealDensityGPerCm2 == 0.f);
609 if (isNoopMaterial) {
610 return true;
611 }
612
613 const float tBefore = scratchState.parameters[3];
614 const float kBefore = scratchState.parameters[4];
615 const float A = 1.f + tBefore * tBefore;
616 const float h = highlandTheta2Rad2;
617 const float R = relativeInverseMomentumVariance;
619 // Barrel slot 2 is sin(phi); disk slot 2 is phi itself.
620 const float snp = scratchState.parameters[2];
621 const float c2 = 1.f - snp * snp;
622 scratchState.covariance[packedCovarianceIndex(2, 2)] += h * A * c2;
623 } else {
624 scratchState.covariance[packedCovarianceIndex(2, 2)] += h * A;
625 }
626 scratchState.covariance[packedCovarianceIndex(3, 3)] += h * A * A;
627 scratchState.covariance[packedCovarianceIndex(4, 3)] += h * A * tBefore * kBefore;
628 scratchState.covariance[packedCovarianceIndex(4, 4)] += h * (tBefore * kBefore) * (tBefore * kBefore) + kBefore * kBefore * R;
630 sanitizeCovariance(scratchState, kBarrelMaxDiagonal);
631 }
632
633 // The equality branch preserves the exact no-op invariant for the
634 // MCS-only-with-unchanged-momentum case (xOverX0 > 0, arealDensity == 0):
635 // x == y implies kAfter == kBefore bit-for-bit with no division rounding.
636 // The nonzero-change branch keeps the accepted/legacy left-to-right
637 // arithmetic (multiply, then divide) rather than dividing the momenta
638 // first, which would prematurely underflow for extreme momentum ratios
639 // and would not reproduce the retained nonzero-material rounding.
640 const float kAfter = (momentumBeforeGeV == momentumAfterGeV)
641 ? kBefore
642 : (kBefore * momentumBeforeGeV) / momentumAfterGeV;
643 scratchState.parameters[4] = kAfter;
644
645 // Only covariance and inverse transverse momentum changed; the coordinate
646 // preconditions checked above still hold.
647 if (scratchState.parameters[4] == 0.f) {
648 return false;
649 }
650 float momentumAfterDerived = scratchState.getP();
651 if (!covarianceDiagonalsNonNegative(scratchState)) {
652 return false;
653 }
654
655 // Energy loss changes q/pT in the covariance-bearing state and its
656 // incidence reference by the same pBefore/pAfter factor. The equality
657 // branch keeps MCS-only corrections bit-exact.
658 const float referenceKBefore = scratchReference.parameters[4];
659 scratchReference.parameters[4] = (momentumBeforeGeV == momentumAfterGeV)
660 ? referenceKBefore
661 : (referenceKBefore * momentumBeforeGeV) / momentumAfterGeV;
662 if (scratchReference.parameters[4] == 0.f || !std::isfinite(scratchReference.parameters[4])) {
663 return false;
664 }
665
666 state = scratchState;
667 incidenceReference = scratchReference;
668 return true;
669}
670
671bool Propagator::attachMeasurement(SurfaceTrackState& state, const SurfaceDescriptor& targetSurface,
672 const SurfaceMeasurement& measurement, float bz,
674 bool chi2GateEnabled, float maxChi2, float& chi2) noexcept
675{
676 if (!acceptsAttachmentChi2(0.f, chi2GateEnabled, maxChi2)) {
677 return false;
678 }
679
680 AttachmentTransaction transaction{state, chi2};
681 auto& scratch = transaction.state;
682 if (!convertKind(scratch, targetSurface.kind, bz)) {
683 return false;
684 }
685 const auto materialBudget = targetSurface.material;
686 float predictedChi2 = 0.f;
687 float updateChi2 = 0.f;
688 const material::IntegratedMaterialBudget integratedMaterial{materialBudget.xOverX0, materialBudget.arealDensityGPerCm2};
689 if (scratch.kind == SurfaceKind::Cylinder) {
690 if (!rotateBarrel(scratch, measurement.frame.frameAngle) ||
691 !propagateBarrel(scratch, measurement.frame.q, bz)) {
692 return false;
693 }
694 SurfaceTrackParameters incidenceReference{scratch};
695 const auto materialResult = correctForMaterial(scratch, incidenceReference, integratedMaterial, direction);
696 if (!materialResult) {
697 return false;
698 }
699 if (!predictedChi2Barrel(scratch, measurement, predictedChi2)) {
700 return false;
701 }
702 if (!acceptsAttachmentChi2(predictedChi2, chi2GateEnabled, maxChi2)) {
703 return false;
704 }
705 if (!updateBarrel(scratch, measurement, updateChi2)) {
706 return false;
707 }
708 } else if (scratch.kind == SurfaceKind::Disk) {
709 if (!propagateToReference(scratch, measurement.frame.q, bz)) {
710 return false;
711 }
712 SurfaceTrackParameters incidenceReference{scratch};
713 const auto materialResult = correctForMaterial(scratch, incidenceReference, integratedMaterial, direction);
714 if (!materialResult) {
715 return false;
716 }
717 if (!predictedChi2Forward(scratch, measurement, predictedChi2)) {
718 return false;
719 }
720 if (!acceptsAttachmentChi2(predictedChi2, chi2GateEnabled, maxChi2)) {
721 return false;
722 }
723 if (!updateForward(scratch, measurement, updateChi2)) {
724 return false;
725 }
726 } else {
727 return false;
728 }
729 transaction.chi2 += updateChi2;
730 transaction.commit(state, chi2);
731 return true;
732}
733
734bool Propagator::propagateToReference(SurfaceTrackState& state, float targetReferenceCoordinate, float bz) noexcept
735{
737 return propagateBarrel(state, targetReferenceCoordinate, bz);
738 }
740 return propagateForward(state, targetReferenceCoordinate, bz);
741 }
742
743 return false;
744}
745
746bool Propagator::propagateToReference(SurfaceTrackState& state, SurfaceTrackParameters& linRef,
747 float targetReferenceCoordinate, float bz) noexcept
748{
749 if (state.kind != linRef.kind) {
750 return false;
751 }
753 return propagateBarrel(state, linRef, targetReferenceCoordinate, bz);
754 }
756 return propagateForward(state, linRef, targetReferenceCoordinate, bz);
757 }
758
759 return false;
760}
761
762bool Propagator::convertKind(SurfaceTrackState& state, SurfaceKind targetKind, float bz) noexcept
763{
764 if (targetKind != SurfaceKind::Cylinder && targetKind != SurfaceKind::Disk) {
765 return false;
766 }
768 return false;
769 }
770 if (state.kind == targetKind) {
771 return true;
772 }
773 auto finiteState = [](const SurfaceTrackState& value) {
774 if (!std::isfinite(value.referenceCoordinate) || !std::isfinite(value.alpha)) {
775 return false;
776 }
777 for (float parameter : value.parameters) {
778 if (!std::isfinite(parameter)) {
779 return false;
780 }
781 }
782 for (float covariance : value.covariance) {
783 if (!std::isfinite(covariance)) {
784 return false;
785 }
786 }
787 return true;
788 };
789 if (!std::isfinite(bz) || !finiteState(state)) {
790 return false;
791 }
792 SurfaceTrackState scratch = state;
793 const bool converted = targetKind == SurfaceKind::Disk ? barrelToForward(scratch, bz)
794 : forwardToBarrel(scratch, bz);
795 if (!converted || !finiteState(scratch)) {
796 return false;
797 }
798 state = scratch;
799 return true;
800}
801
802bool Propagator::propagateToMeasurement(SurfaceTrackState& state, SurfaceTrackParameters& linRef,
803 const SurfaceDescriptor& targetSurface, const SurfaceMeasurement& targetMeasurement,
804 float bz, material::MaterialTraversalDirection direction,
805 bool chi2GateEnabled, float maxChi2, float& chi2,
806 bool shiftReferenceToMeasurement) noexcept
807{
808 if (chi2 < 0.f) {
809 return false;
810 }
811 if (!acceptsAttachmentChi2(0.f, chi2GateEnabled, maxChi2)) {
812 return false;
813 }
814
815 const SurfaceKind targetKind = targetSurface.kind;
816 if (targetKind == SurfaceKind::Undefined) {
817 return false;
818 }
819
820 AttachmentTransaction transaction{state, chi2};
821 auto& scratchState = transaction.state;
822 SurfaceTrackParameters scratchRef = linRef;
823
824 if (scratchState.kind != targetKind) {
825 if (!convertKind(scratchState, targetKind, bz)) {
826 return false;
827 }
828 // Changing parameter conventions is also a relinearization boundary.
829 // The conversion Jacobian is evaluated at scratchState, so begin the
830 // target-kind propagation from that same point.
831 scratchRef = SurfaceTrackParameters{scratchState};
832 }
833
834 const material::IntegratedMaterialBudget materialBudget{targetSurface.material.xOverX0, targetSurface.material.arealDensityGPerCm2};
835 auto& scratchChi2 = transaction.chi2;
836 float predChi2 = 0.f;
837 float updateChi2 = 0.f;
838
839 if (targetKind == SurfaceKind::Cylinder) {
840 if (!rotateBarrel(scratchState, scratchRef, targetMeasurement.frame.frameAngle, bz)) {
841 return false;
842 }
843 if (!propagateBarrel(scratchState, scratchRef, targetMeasurement.frame.q, bz)) {
844 return false;
845 }
846 clampNegligibleCovarianceNoise(scratchState);
847 const auto materialResult = correctForMaterial(scratchState, scratchRef, materialBudget, direction);
848 if (!materialResult) {
849 return false;
850 }
851 if (!predictedChi2Barrel(scratchState, targetMeasurement, predChi2)) {
852 return false;
853 }
854 } else {
855 if (!Propagator::propagateToReference(scratchState, scratchRef, targetMeasurement.frame.q, bz)) {
856 return false;
857 }
858 clampNegligibleCovarianceNoise(scratchState);
859 const auto materialResult = correctForMaterial(scratchState, scratchRef, materialBudget, direction);
860 if (!materialResult) {
861 return false;
862 }
863 if (!predictedChi2Forward(scratchState, targetMeasurement, predChi2)) {
864 return false;
865 }
866 }
867
868 if (!acceptsAttachmentChi2(predChi2, chi2GateEnabled, maxChi2)) {
869 return false;
870 }
871
872 if (targetKind == SurfaceKind::Cylinder) {
873 if (!updateBarrel(scratchState, targetMeasurement, updateChi2)) {
874 return false;
875 }
876 } else {
877 if (!updateForward(scratchState, targetMeasurement, updateChi2)) {
878 return false;
879 }
880 }
881 scratchChi2 += updateChi2;
882 if (scratchChi2 < 0.f) {
883 return false;
884 }
885
886 if (shiftReferenceToMeasurement) {
887 if (targetKind == SurfaceKind::Cylinder) {
888 if (!shiftReferenceToMeasurementBarrel(scratchRef, targetMeasurement)) {
889 return false;
890 }
891 } else {
892 if (!shiftReferenceToMeasurementForward(scratchRef, targetMeasurement)) {
893 return false;
894 }
895 }
896 }
897
898 transaction.commit(state, chi2);
899 linRef = scratchRef;
900 return true;
901}
902
903bool Propagator::rotateBarrel(SurfaceTrackState& state, float targetAlpha) noexcept
904{
905 if (!validateBarrelSource(state)) {
906 return false;
907 }
908 SurfaceTrackState scratch = state;
909 const float canonicalTargetAlpha = std::remainder(targetAlpha, 2.f * o2::constants::math::PI);
910 const float delta = std::remainder(canonicalTargetAlpha - scratch.alpha, 2.f * o2::constants::math::PI);
911 const float sine = std::sin(delta);
912 const float cosine = std::cos(delta);
913 const float snp = scratch.parameters[2];
914 if (std::abs(snp) >= 1.f) {
915 return false;
916 }
917 const float csp = std::sqrt((1.f - snp) * (1.f + snp));
918 const float rotatedCosine = csp * cosine + snp * sine;
919 const float rotatedSnp = snp * cosine - csp * sine;
920 if (rotatedCosine < 0.f || std::abs(rotatedSnp) >= 1.f || csp == 0.f) {
921 return false;
922 }
923 const float x = scratch.referenceCoordinate;
924 const float y = scratch.parameters[0];
925 scratch.referenceCoordinate = x * cosine + y * sine;
926 scratch.parameters[0] = -x * sine + y * cosine;
927 scratch.parameters[2] = rotatedSnp;
928 scratch.alpha = canonicalTargetAlpha;
929 const float ratio = cosine + snp / csp * sine;
930 scratch.covariance[packedCovarianceIndex(0, 0)] *= cosine * cosine;
931 scratch.covariance[packedCovarianceIndex(1, 0)] *= cosine;
932 scratch.covariance[packedCovarianceIndex(2, 0)] *= cosine * ratio;
933 scratch.covariance[packedCovarianceIndex(2, 1)] *= ratio;
934 scratch.covariance[packedCovarianceIndex(2, 2)] *= ratio * ratio;
935 scratch.covariance[packedCovarianceIndex(3, 0)] *= cosine;
936 scratch.covariance[packedCovarianceIndex(3, 2)] *= ratio;
937 scratch.covariance[packedCovarianceIndex(4, 0)] *= cosine;
938 scratch.covariance[packedCovarianceIndex(4, 2)] *= ratio;
939 return commitBarrelPropagation(state, scratch);
940}
941
942bool Propagator::propagateBarrel(SurfaceTrackState& state, float targetX, float bz) noexcept
943{
944 if (!validateBarrelSource(state)) {
945 return false;
946 }
947 SurfaceTrackState scratch = state;
948 const float dx = targetX - scratch.referenceCoordinate;
949 if (dx == 0.f) {
950 scratch.referenceCoordinate = targetX;
951 return commitBarrelPropagation(state, scratch);
952 }
953 const float snp = scratch.parameters[2];
954 const float curvature = scratch.parameters[4] * bz * o2::constants::math::B2C;
955 const float propagatedSnp = snp + curvature * dx;
956 if (std::abs(snp) >= 1.f || std::abs(propagatedSnp) >= 1.f) {
957 return false;
958 }
959 const float csp = std::sqrt((1.f - snp) * (1.f + snp));
960 const float propagatedCsp = std::sqrt((1.f - propagatedSnp) * (1.f + propagatedSnp));
961 if (csp == 0.f || propagatedCsp == 0.f) {
962 return false;
963 }
964 const float reciprocalCosines = 1.f / (csp + propagatedCsp);
965 const float dyOverDx = (snp + propagatedSnp) * reciprocalCosines;
966 const float x2r = curvature * dx;
967 const bool arcZ = std::abs(x2r) > 0.05f;
968 float dz = 0.f;
969 if (arcZ) {
970 const float argument = csp * propagatedSnp - propagatedCsp * snp;
971 if (std::abs(argument) > 1.f || curvature == 0.f) {
972 return false;
973 }
974 float angle = std::asin(argument);
975 if (snp * snp + propagatedSnp * propagatedSnp > 1.f && snp * propagatedSnp < 0.f) {
976 angle = propagatedSnp > 0.f ? o2::constants::math::PI - angle : -o2::constants::math::PI - angle;
977 }
978 dz = scratch.parameters[3] / curvature * angle;
979 } else {
980 dz = dx * (propagatedCsp + propagatedSnp * dyOverDx) * scratch.parameters[3];
981 }
982 scratch.referenceCoordinate = targetX;
983 scratch.parameters[0] += dx * dyOverDx;
984 scratch.parameters[1] += dz;
985 scratch.parameters[2] = propagatedSnp;
986
987 const float propagatedCspInverse = 1.f / propagatedCsp;
988 const float dxOverCosines = dx * reciprocalCosines;
989 const float hh = dxOverCosines * propagatedCspInverse * (1.f + csp * propagatedCsp + snp * propagatedSnp);
990 const float jj = dx * (dyOverDx - propagatedSnp * propagatedCspInverse);
991 DenseMatrix5 jacobian{};
992 identity(jacobian);
993 jacobian[0][2] = hh / csp;
994 jacobian[0][4] = hh * dxOverCosines * bz * o2::constants::math::B2C;
995 jacobian[1][2] = scratch.parameters[3] * (jacobian[0][2] * propagatedSnp + jj);
996 jacobian[1][3] = dx * (propagatedCsp + propagatedSnp * dyOverDx);
997 jacobian[1][4] = scratch.parameters[3] * (jacobian[0][4] * propagatedSnp + jj * dx * bz * o2::constants::math::B2C);
998 jacobian[2][4] = dx * bz * o2::constants::math::B2C;
999 transportCovariance(scratch, jacobian);
1000 return commitBarrelPropagation(state, scratch);
1001}
1002
1003bool Propagator::predictedChi2Barrel(const SurfaceTrackState& state, const SurfaceMeasurement& measurement, float& chi2) noexcept
1004{
1005 if (!validateBarrelSource(state)) {
1006 return false;
1007 }
1008 float inverse00 = 0.f;
1009 float inverse01 = 0.f;
1010 float inverse11 = 0.f;
1011 if (!residualInverse(state, measurement, inverse00, inverse01, inverse11)) {
1012 return false;
1013 }
1014 const float residualY = measurement.frame.u - state.parameters[0];
1015 const float residualZ = measurement.frame.v - state.parameters[1];
1016 const float scratchChi2 = residualY * (inverse00 * residualY + inverse01 * residualZ) +
1017 residualZ * (inverse01 * residualY + inverse11 * residualZ);
1018 chi2 = scratchChi2;
1019 return true;
1020}
1021
1022bool Propagator::updateBarrel(SurfaceTrackState& state, const SurfaceMeasurement& measurement, float& chi2) noexcept
1023{
1024 if (!validateBarrelSource(state)) {
1025 return false;
1026 }
1027 float inverse00 = 0.f;
1028 float inverse01 = 0.f;
1029 float inverse11 = 0.f;
1030 if (!residualInverse(state, measurement, inverse00, inverse01, inverse11)) {
1031 return false;
1032 }
1033 DenseMatrix5 covariance{};
1034 DenseMatrix5 josephTransform{};
1035 DenseMatrix5 transformedCovariance{};
1036 DenseMatrix5 updatedCovariance{};
1037 float gain[5][2]{};
1038 unpackCovariance(state, covariance);
1039 const float residual[2] = {measurement.frame.u - state.parameters[0], measurement.frame.v - state.parameters[1]};
1040 SurfaceTrackState scratch = state;
1041 for (uint8_t row = 0; row < 5; ++row) {
1042 gain[row][0] = covariance[row][0] * inverse00 + covariance[row][1] * inverse01;
1043 gain[row][1] = covariance[row][0] * inverse01 + covariance[row][1] * inverse11;
1044 scratch.parameters[row] += gain[row][0] * residual[0] + gain[row][1] * residual[1];
1045 }
1046
1047 // Joseph covariance update: (I - K H) P (I - K H)^T + K R K^T.
1048 // The surface measurement matrix H selects state parameters 0 and 1.
1049 identity(josephTransform);
1050 for (uint8_t row = 0; row < 5; ++row) {
1051 josephTransform[row][0] -= gain[row][0];
1052 josephTransform[row][1] -= gain[row][1];
1053 }
1054 for (uint8_t row = 0; row < 5; ++row) {
1055 for (uint8_t column = 0; column < 5; ++column) {
1056 for (uint8_t inner = 0; inner < 5; ++inner) {
1057 transformedCovariance[row][column] += josephTransform[row][inner] * covariance[inner][column];
1058 }
1059 }
1060 }
1061 for (uint8_t row = 0; row < 5; ++row) {
1062 for (uint8_t column = 0; column < 5; ++column) {
1063 for (uint8_t inner = 0; inner < 5; ++inner) {
1064 updatedCovariance[row][column] += transformedCovariance[row][inner] * josephTransform[column][inner];
1065 }
1066 updatedCovariance[row][column] +=
1067 gain[row][0] * (measurement.covariance.uu * gain[column][0] + measurement.covariance.uv * gain[column][1]) +
1068 gain[row][1] * (measurement.covariance.uv * gain[column][0] + measurement.covariance.vv * gain[column][1]);
1069 }
1070 }
1071 for (uint8_t row = 0; row < 5; ++row) {
1072 for (uint8_t column = 0; column < row; ++column) {
1073 const float symmetric = 0.5f * (updatedCovariance[row][column] + updatedCovariance[column][row]);
1074 updatedCovariance[row][column] = symmetric;
1075 updatedCovariance[column][row] = symmetric;
1076 }
1077 }
1078 packCovariance(updatedCovariance, scratch);
1079 const float scratchChi2 = residual[0] * (inverse00 * residual[0] + inverse01 * residual[1]) +
1080 residual[1] * (inverse01 * residual[0] + inverse11 * residual[1]);
1081 // Preserve the established covariance bounds after the Joseph update.
1082 sanitizeCovariance(scratch, kBarrelMaxDiagonal);
1083 state = scratch;
1084 chi2 = scratchChi2;
1085 return true;
1086}
1087
1088bool Propagator::rotateBarrel(SurfaceTrackState& state, SurfaceTrackParameters& linRef, float targetAlpha, float bz) noexcept
1089{
1090 if (!validateBarrelSource(state)) {
1091 return false;
1092 }
1093 if (linRef.kind != SurfaceKind::Cylinder) {
1094 return false;
1095 }
1096 // Pairing requires exact referenceCoordinate/alpha equality. Parameters may
1097 // differ because linRef is a linearization reference.
1098 if (state.referenceCoordinate != linRef.referenceCoordinate) {
1099 return false;
1100 }
1101 if (state.alpha != linRef.alpha) {
1102 return false;
1103 }
1104 const float stateSnp = state.parameters[2];
1105 if (std::abs(stateSnp) >= 1.f) {
1106 return false;
1107 }
1108
1109 SurfaceTrackState scratchState = state;
1110 SurfaceTrackParameters scratchRef = linRef;
1111
1112 const float canonicalAlpha = std::remainder(targetAlpha, 2.f * o2::constants::math::PI);
1113
1114 // Rotate the reference using its own pre-rotation snp.
1115 const float refSnpBefore = scratchRef.parameters[2];
1116 if (std::abs(refSnpBefore) >= 1.f) {
1117 return false;
1118 }
1119 const float delta = std::remainder(canonicalAlpha - scratchRef.alpha, 2.f * o2::constants::math::PI);
1120 const float sa = std::sin(delta);
1121 const float ca = std::cos(delta);
1122 const float refCsp0 = std::sqrt((1.f - refSnpBefore) * (1.f + refSnpBefore));
1123 if (refCsp0 * ca + refSnpBefore * sa < 0.f) {
1124 return false;
1125 }
1126 const float refSnpRotated = refSnpBefore * ca - refCsp0 * sa;
1127 if (std::abs(refSnpRotated) >= 1.f) {
1128 return false;
1129 }
1130 const float refXOld = scratchRef.referenceCoordinate;
1131 const float refYOld = scratchRef.parameters[0];
1132 scratchRef.alpha = canonicalAlpha;
1133 scratchRef.referenceCoordinate = refXOld * ca + refYOld * sa;
1134 scratchRef.parameters[0] = -refXOld * sa + refYOld * ca;
1135 scratchRef.parameters[2] = refSnpRotated;
1136
1137 // Rotate the state's pre-rotation X,Y by the reference delta.
1138 const float trackX = scratchState.referenceCoordinate * ca + scratchState.parameters[0] * sa;
1139
1140 if (!propagateReferenceParams(scratchRef, trackX, bz)) {
1141 return false;
1142 }
1143
1144 // Rotate the state using its own snp and post-rotation validity.
1145 const float csp = std::sqrt((1.f - stateSnp) * (1.f + stateSnp));
1146 if (csp * ca + stateSnp * sa < 0.f) {
1147 return false;
1148 }
1149 const float updatedSnp = stateSnp * ca - csp * sa;
1150 if (std::abs(updatedSnp) >= 1.f) {
1151 return false;
1152 }
1153 const float stateXOld = scratchState.referenceCoordinate;
1154 const float stateYOld = scratchState.parameters[0];
1155 scratchState.parameters[0] = -stateXOld * sa + stateYOld * ca;
1156 scratchState.referenceCoordinate = trackX;
1157 scratchState.parameters[2] = updatedSnp;
1158 scratchState.alpha = canonicalAlpha;
1159
1160 // Evaluate the covariance Jacobian at the reference, not the state's snp.
1161 // Compute cspRef1 algebraically to match the legacy formula.
1162 const float cspRef1 = ca * refCsp0 + sa * refSnpBefore;
1163 if (cspRef1 == 0.f) {
1164 return false;
1165 }
1166 const float rr = cspRef1 / refCsp0;
1167
1168 // Compute the extra lower-triangle row before the plane-rotation multiplies,
1169 // matching the legacy evaluation order.
1170 const float cXSigY = scratchState.covariance[packedCovarianceIndex(0, 0)] * ca * sa;
1171 const float cXSigZ = scratchState.covariance[packedCovarianceIndex(1, 0)] * sa;
1172 const float cXSigSnp = scratchState.covariance[packedCovarianceIndex(2, 0)] * rr * sa;
1173 const float cXSigTgl = scratchState.covariance[packedCovarianceIndex(3, 0)] * sa;
1174 const float cXSigQ2Pt = scratchState.covariance[packedCovarianceIndex(4, 0)] * sa;
1175 const float cSigX2 = scratchState.covariance[packedCovarianceIndex(0, 0)] * sa * sa;
1176
1177 scratchState.covariance[packedCovarianceIndex(0, 0)] *= ca * ca;
1178 scratchState.covariance[packedCovarianceIndex(1, 0)] *= ca;
1179 scratchState.covariance[packedCovarianceIndex(2, 0)] *= ca * rr;
1180 scratchState.covariance[packedCovarianceIndex(2, 1)] *= rr;
1181 scratchState.covariance[packedCovarianceIndex(2, 2)] *= rr * rr;
1182 scratchState.covariance[packedCovarianceIndex(3, 0)] *= ca;
1183 scratchState.covariance[packedCovarianceIndex(3, 2)] *= rr;
1184 scratchState.covariance[packedCovarianceIndex(4, 0)] *= ca;
1185 scratchState.covariance[packedCovarianceIndex(4, 2)] *= rr;
1186
1187 const float cspRef1Inv = 1.f / cspRef1;
1188 const float j3 = -refSnpRotated * cspRef1Inv;
1189 const float j4 = -scratchRef.parameters[3] * cspRef1Inv;
1190 const float j5 = scratchRef.parameters[4] * bz * o2::constants::math::B2C;
1191
1192 const float hXSigY = cXSigY + cSigX2 * j3;
1193 const float hXSigZ = cXSigZ + cSigX2 * j4;
1194 const float hXSigSnp = cXSigSnp + cSigX2 * j5;
1195
1196 scratchState.covariance[packedCovarianceIndex(0, 0)] += j3 * (cXSigY + hXSigY);
1197 scratchState.covariance[packedCovarianceIndex(1, 1)] += j4 * (cXSigZ + hXSigZ);
1198 scratchState.covariance[packedCovarianceIndex(2, 0)] += cXSigSnp * j3 + hXSigY * j5;
1199 scratchState.covariance[packedCovarianceIndex(2, 2)] += j5 * (cXSigSnp + hXSigSnp);
1200 scratchState.covariance[packedCovarianceIndex(3, 1)] += cXSigTgl * j4;
1201 scratchState.covariance[packedCovarianceIndex(4, 0)] += cXSigQ2Pt * j3;
1202 scratchState.covariance[packedCovarianceIndex(4, 2)] += cXSigQ2Pt * j5;
1203
1204 scratchState.covariance[packedCovarianceIndex(1, 0)] += cXSigZ * j3 + hXSigY * j4;
1205 scratchState.covariance[packedCovarianceIndex(2, 1)] += cXSigSnp * j4 + hXSigZ * j5;
1206 scratchState.covariance[packedCovarianceIndex(3, 0)] += cXSigTgl * j3;
1207 scratchState.covariance[packedCovarianceIndex(3, 2)] += cXSigTgl * j5;
1208 scratchState.covariance[packedCovarianceIndex(4, 1)] += cXSigQ2Pt * j4;
1209
1210 sanitizeCovariance(scratchState, kBarrelMaxDiagonal);
1211 state = scratchState;
1212 linRef = scratchRef;
1213 return true;
1214}
1215
1216bool Propagator::propagateBarrel(SurfaceTrackState& state, SurfaceTrackParameters& linRef, float targetX, float bz) noexcept
1217{
1218 if (!validateBarrelSource(state)) {
1219 return false;
1220 }
1221 if (linRef.kind != SurfaceKind::Cylinder) {
1222 return false;
1223 }
1224 // Pairing requires exact referenceCoordinate/alpha equality; parameters may
1225 // differ.
1226 if (state.referenceCoordinate != linRef.referenceCoordinate) {
1227 return false;
1228 }
1229 if (state.alpha != linRef.alpha) {
1230 return false;
1231 }
1232
1233 const float dx = targetX - state.referenceCoordinate;
1234 if (std::abs(dx) < o2::constants::math::Almost0) {
1235 SurfaceTrackState scratchState = state;
1236 SurfaceTrackParameters scratchRef = linRef;
1237 scratchState.referenceCoordinate = targetX;
1238 scratchRef.referenceCoordinate = targetX;
1239 state = scratchState;
1240 linRef = scratchRef;
1241 return true;
1242 }
1243
1244 SurfaceTrackParameters scratchRef = linRef;
1245 const float snpRef0 = scratchRef.parameters[2];
1246 const float cspRef0 = std::sqrt((1.f - snpRef0) * (1.f + snpRef0));
1247 const float tglRef0 = scratchRef.parameters[3];
1248
1249 if (!propagateReferenceParams(scratchRef, targetX, bz)) {
1250 return false;
1251 }
1252 const float snpRef1 = scratchRef.parameters[2];
1253 const float cspRef1 = std::sqrt((1.f - snpRef1) * (1.f + snpRef1));
1254 if (cspRef0 == 0.f || cspRef1 == 0.f) {
1255 return false;
1256 }
1257
1258 const float kb = bz * o2::constants::math::B2C;
1259 const float cspRef0Inv = 1.f / cspRef0;
1260 const float cspRef1Inv = 1.f / cspRef1;
1261 const float cc = cspRef0 + cspRef1;
1262 const float ccInv = 1.f / cc;
1263 const float dy2dx = (snpRef0 + snpRef1) * ccInv;
1264 const float dxccInv = dx * ccInv;
1265 const float hh = dxccInv * cspRef1Inv * (1.f + cspRef0 * cspRef1 + snpRef0 * snpRef1);
1266 const float jj = dx * (dy2dx - snpRef1 * cspRef1Inv);
1267
1268 const float f02 = hh * cspRef0Inv;
1269 const float f04 = hh * dxccInv * kb;
1270 const float f24 = dx * kb;
1271 const float f12 = tglRef0 * (f02 * snpRef1 + jj);
1272 const float f13 = dx * (cspRef1 + snpRef1 * dy2dx);
1273 const float f14 = tglRef0 * (f04 * snpRef1 + jj * f24);
1274
1275 float diff[5];
1276 for (uint8_t i = 0; i < 5; ++i) {
1277 diff[i] = state.parameters[i] - linRef.parameters[i];
1278 }
1279 const float snpUpd = snpRef1 + diff[2] + f24 * diff[4];
1280 if (std::abs(snpUpd) >= 1.f) {
1281 return false;
1282 }
1283
1284 SurfaceTrackState scratchState = state;
1285 scratchState.referenceCoordinate = targetX;
1286 scratchState.parameters[0] = scratchRef.parameters[0] + diff[0] + f02 * diff[2] + f04 * diff[4];
1287 scratchState.parameters[1] = scratchRef.parameters[1] + diff[1] + f13 * diff[3] + f14 * diff[4];
1288 scratchState.parameters[2] = snpUpd;
1289 scratchState.parameters[3] = scratchRef.parameters[3] + diff[3];
1290 scratchState.parameters[4] = scratchRef.parameters[4] + diff[4];
1291
1292 const float c00 = state.covariance[packedCovarianceIndex(0, 0)];
1293 const float c10 = state.covariance[packedCovarianceIndex(1, 0)];
1294 const float c11 = state.covariance[packedCovarianceIndex(1, 1)];
1295 const float c20 = state.covariance[packedCovarianceIndex(2, 0)];
1296 const float c21 = state.covariance[packedCovarianceIndex(2, 1)];
1297 const float c22 = state.covariance[packedCovarianceIndex(2, 2)];
1298 const float c30 = state.covariance[packedCovarianceIndex(3, 0)];
1299 const float c31 = state.covariance[packedCovarianceIndex(3, 1)];
1300 const float c32 = state.covariance[packedCovarianceIndex(3, 2)];
1301 const float c33 = state.covariance[packedCovarianceIndex(3, 3)];
1302 const float c40 = state.covariance[packedCovarianceIndex(4, 0)];
1303 const float c41 = state.covariance[packedCovarianceIndex(4, 1)];
1304 const float c42 = state.covariance[packedCovarianceIndex(4, 2)];
1305 const float c43 = state.covariance[packedCovarianceIndex(4, 3)];
1306 const float c44 = state.covariance[packedCovarianceIndex(4, 4)];
1307
1308 const float b00 = f02 * c20 + f04 * c40;
1309 const float b01 = f12 * c20 + f14 * c40 + f13 * c30;
1310 const float b02 = f24 * c40;
1311 const float b10 = f02 * c21 + f04 * c41;
1312 const float b11 = f12 * c21 + f14 * c41 + f13 * c31;
1313 const float b12 = f24 * c41;
1314 const float b20 = f02 * c22 + f04 * c42;
1315 const float b21 = f12 * c22 + f14 * c42 + f13 * c32;
1316 const float b22 = f24 * c42;
1317 const float b40 = f02 * c42 + f04 * c44;
1318 const float b41 = f12 * c42 + f14 * c44 + f13 * c43;
1319 const float b42 = f24 * c44;
1320 const float b30 = f02 * c32 + f04 * c43;
1321 const float b31 = f12 * c32 + f14 * c43 + f13 * c33;
1322 const float b32 = f24 * c43;
1323
1324 const float a00 = f02 * b20 + f04 * b40;
1325 const float a01 = f02 * b21 + f04 * b41;
1326 const float a02 = f02 * b22 + f04 * b42;
1327 const float a11 = f12 * b21 + f14 * b41 + f13 * b31;
1328 const float a12 = f12 * b22 + f14 * b42 + f13 * b32;
1329 const float a22 = f24 * b42;
1330
1331 scratchState.covariance[packedCovarianceIndex(0, 0)] = c00 + b00 + b00 + a00;
1332 scratchState.covariance[packedCovarianceIndex(1, 0)] = c10 + b10 + b01 + a01;
1333 scratchState.covariance[packedCovarianceIndex(2, 0)] = c20 + b20 + b02 + a02;
1334 scratchState.covariance[packedCovarianceIndex(3, 0)] = c30 + b30;
1335 scratchState.covariance[packedCovarianceIndex(4, 0)] = c40 + b40;
1336 scratchState.covariance[packedCovarianceIndex(1, 1)] = c11 + b11 + b11 + a11;
1337 scratchState.covariance[packedCovarianceIndex(2, 1)] = c21 + b21 + b12 + a12;
1338 scratchState.covariance[packedCovarianceIndex(3, 1)] = c31 + b31;
1339 scratchState.covariance[packedCovarianceIndex(4, 1)] = c41 + b41;
1340 scratchState.covariance[packedCovarianceIndex(2, 2)] = c22 + b22 + b22 + a22;
1341 scratchState.covariance[packedCovarianceIndex(3, 2)] = c32 + b32;
1342 scratchState.covariance[packedCovarianceIndex(4, 2)] = c42 + b42;
1343 scratchState.covariance[packedCovarianceIndex(3, 3)] = c33;
1344 scratchState.covariance[packedCovarianceIndex(4, 3)] = c43;
1345 scratchState.covariance[packedCovarianceIndex(4, 4)] = c44;
1346
1347 // A large Jacobian step can invalidate covariance through an off-diagonal
1348 // term even when all diagonals look valid. Sanitize before committing.
1349 sanitizeCovariance(scratchState, kBarrelMaxDiagonal);
1350 state = scratchState;
1351 linRef = scratchRef;
1352 return true;
1353}
1354
1355bool Propagator::shiftReferenceToMeasurementBarrel(SurfaceTrackParameters& linRef, const SurfaceMeasurement& measurement) noexcept
1356{
1357 if (linRef.kind != SurfaceKind::Cylinder) {
1358 return false;
1359 }
1360 SurfaceTrackParameters scratch = linRef;
1361 scratch.parameters[0] = measurement.frame.u;
1362 scratch.parameters[1] = measurement.frame.v;
1363 linRef = scratch;
1364 return true;
1365}
1366
1367bool Propagator::predictedChi2Forward(const SurfaceTrackState& state, const SurfaceMeasurement& measurement, float& chi2) noexcept
1368{
1369 if (!validateForwardSource(state)) {
1370 return false;
1371 }
1372 float inverse00 = 0.f;
1373 float inverse01 = 0.f;
1374 float inverse11 = 0.f;
1375 if (!residualInverse(state, measurement, inverse00, inverse01, inverse11)) {
1376 return false;
1377 }
1378 const float residualX = measurement.frame.u - state.parameters[0];
1379 const float residualY = measurement.frame.v - state.parameters[1];
1380 const float scratchChi2 = residualX * (inverse00 * residualX + inverse01 * residualY) +
1381 residualY * (inverse01 * residualX + inverse11 * residualY);
1382 chi2 = scratchChi2;
1383 return true;
1384}
1385
1386bool Propagator::updateForward(SurfaceTrackState& state, const SurfaceMeasurement& measurement, float& chi2) noexcept
1387{
1388 if (!validateForwardSource(state)) {
1389 return false;
1390 }
1391 float inverse00 = 0.f;
1392 float inverse01 = 0.f;
1393 float inverse11 = 0.f;
1394 if (!residualInverse(state, measurement, inverse00, inverse01, inverse11)) {
1395 return false;
1396 }
1397
1398 DenseMatrix5 covariance{};
1399 DenseMatrix5 josephTransform{};
1400 DenseMatrix5 transformedCovariance{};
1401 DenseMatrix5 updatedCovariance{};
1402 float gain[5][2]{};
1403 unpackCovariance(state, covariance);
1404 const float residual[2] = {measurement.frame.u - state.parameters[0], measurement.frame.v - state.parameters[1]};
1405 SurfaceTrackState scratch = state;
1406 for (uint8_t row = 0; row < 5; ++row) {
1407 gain[row][0] = covariance[row][0] * inverse00 + covariance[row][1] * inverse01;
1408 gain[row][1] = covariance[row][0] * inverse01 + covariance[row][1] * inverse11;
1409 scratch.parameters[row] += gain[row][0] * residual[0] + gain[row][1] * residual[1];
1410 }
1411
1412 // Joseph covariance update: (I - K H) P (I - K H)^T + K R K^T.
1413 // The surface measurement matrix H selects state parameters 0 and 1.
1414 identity(josephTransform);
1415 for (uint8_t row = 0; row < 5; ++row) {
1416 josephTransform[row][0] -= gain[row][0];
1417 josephTransform[row][1] -= gain[row][1];
1418 }
1419 for (uint8_t row = 0; row < 5; ++row) {
1420 for (uint8_t column = 0; column < 5; ++column) {
1421 for (uint8_t inner = 0; inner < 5; ++inner) {
1422 transformedCovariance[row][column] += josephTransform[row][inner] * covariance[inner][column];
1423 }
1424 }
1425 }
1426 for (uint8_t row = 0; row < 5; ++row) {
1427 for (uint8_t column = 0; column < 5; ++column) {
1428 for (uint8_t inner = 0; inner < 5; ++inner) {
1429 updatedCovariance[row][column] += transformedCovariance[row][inner] * josephTransform[column][inner];
1430 }
1431 updatedCovariance[row][column] +=
1432 gain[row][0] * (measurement.covariance.uu * gain[column][0] + measurement.covariance.uv * gain[column][1]) +
1433 gain[row][1] * (measurement.covariance.uv * gain[column][0] + measurement.covariance.vv * gain[column][1]);
1434 }
1435 }
1436 for (uint8_t row = 0; row < 5; ++row) {
1437 for (uint8_t column = 0; column < row; ++column) {
1438 const float symmetric = 0.5f * (updatedCovariance[row][column] + updatedCovariance[column][row]);
1439 updatedCovariance[row][column] = symmetric;
1440 updatedCovariance[column][row] = symmetric;
1441 }
1442 }
1443 packCovariance(updatedCovariance, scratch);
1444 const float scratchChi2 = residual[0] * (inverse00 * residual[0] + inverse01 * residual[1]) +
1445 residual[1] * (inverse01 * residual[0] + inverse11 * residual[1]);
1446 // Preserve the established covariance bounds after the Joseph update.
1447 sanitizeCovariance(scratch, kForwardMaxDiagonal);
1448 state = scratch;
1449 chi2 = scratchChi2;
1450 return true;
1451}
1452
1453bool Propagator::shiftReferenceToMeasurementForward(SurfaceTrackParameters& linRef, const SurfaceMeasurement& measurement) noexcept
1454{
1455 if (linRef.kind != SurfaceKind::Disk) {
1456 return false;
1457 }
1458 SurfaceTrackParameters scratch = linRef;
1459 scratch.parameters[0] = measurement.frame.u;
1460 scratch.parameters[1] = measurement.frame.v;
1461 linRef = scratch;
1462 return true;
1463}
1464
1465bool Propagator::propagateForward(SurfaceTrackState& state, float targetZ, float bz) noexcept
1466{
1467 return propagateAccepted(state, targetZ, bz);
1468}
1469
1470bool Propagator::propagateForward(SurfaceTrackState& state, SurfaceTrackParameters& linRef,
1471 float targetZ, float bz) noexcept
1472{
1473 return propagateAccepted(state, linRef, targetZ, bz);
1474}
1475
1476} // namespace o2::itsmft::tracking
particle ids, masses, names class definition
std::vector< double > sum
int32_t i
SurfaceTrackState state
float chi2
useful math constants
Definition A.h:16
Class for time synchronization of RawReader instances.
GLdouble n
Definition glcorearb.h:1982
GLfloat GLfloat GLfloat alpha
Definition glcorearb.h:279
GLint GLenum GLint x
Definition glcorearb.h:403
const GLfloat * m
Definition glcorearb.h:4066
GLuint index
Definition glcorearb.h:781
GLintptr GLsizeiptr GLboolean commit
Definition glcorearb.h:3613
GLint y
Definition glcorearb.h:270
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLfloat angle
Definition glcorearb.h:4071
GLboolean r
Definition glcorearb.h:1233
GLint ref
Definition glcorearb.h:291
uint8_t itsSharedClusterMap uint8_t
const TrackingFrameInfo *const const Cluster *const const float const float bz
bool calculateMaterialPhysics(float momentumGeV, o2::track::PID pid, uint8_t absCharge, MaterialTraversalDirection direction, IntegratedMaterialBudget material, float &momentumAfterGeV, float &highlandTheta2Rad2, float &relativeInverseMomentumVariance) noexcept
const int float col
float xOverX0
thickness in units of radiation length
std::vector< o2::mch::ChannelCode > cc
std::vector< int > row