Skip to content

Commit aad0f92

Browse files
committed
Improve common ITS/MFT refit initialization
Estimate the first q/pT from all attached hits with a weighted circle fit, using bounded storage and retaining the existing seed for field-off fits. Initialize both surface conventions with diagonal variances (1, 1, 1, 1, clamp(abs(q/pT), 1, 10)); inflate the previous diagonal by 25 and clear correlations for subsequent passes. Add signed-curvature, short-lever-arm and invalid-input coverage. Validated with run3-local3: 15 normalized-refit and 9 failure-contract cases pass, and all 346 assessment objects reproduce the preserved combined-circle MFT result exactly. Baseline fixture and staged library remain unchanged.
1 parent 3820a39 commit aad0f92

2 files changed

Lines changed: 169 additions & 18 deletions

File tree

‎Detectors/ITSMFT/common/tracking/include/ITSMFTTracking/RefitDriver.h‎

Lines changed: 123 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
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
3638
namespace 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+
39119
struct 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.
121201
GPUhdi() 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);

‎Detectors/ITSMFT/common/tracking/test/testMFTNormalizedRefit.cxx‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,3 +535,49 @@ BOOST_AUTO_TEST_CASE(GenericRefitUsesStablePreSortClusterIdentity)
535535
BOOST_CHECK_EQUAL(layerGlobals[layer][0].clusterId, 1u);
536536
}
537537
}
538+
539+
BOOST_AUTO_TEST_CASE(AllPointCircleRecoversSignedCurvatureAtDifferentLeverArms)
540+
{
541+
// Exact helices exercise charge/field signs, rotations and the short
542+
// transverse lever arm of a forward track without tuning to a noisy sample.
543+
for (double bz : {-5., 5.}) {
544+
for (double qOverPt : {-5., -1., -.05, .05, 1., 5.}) {
545+
for (double phi : {-.7, 0., 1.8}) {
546+
for (double scale : {0.01, 1.}) {
547+
std::vector<detail::CircleFitPoint> points;
548+
const double curvature = qOverPt * bz * o2::constants::math::B2C;
549+
for (double arc : {2., 3., 4., 20., 25., 34., 40.}) {
550+
arc *= scale;
551+
const double x = std::sin(curvature * arc) / curvature;
552+
const double y = 2 * std::pow(std::sin(curvature * arc / 2), 2) / curvature;
553+
points.push_back({2 + x * std::cos(phi) - y * std::sin(phi),
554+
-1 + x * std::sin(phi) + y * std::cos(phi), 1.e-6, 2.e-7, 2.e-6});
555+
}
556+
const double fitted = detail::estimateCircleQOverPt(points, bz);
557+
BOOST_REQUIRE(std::isfinite(fitted));
558+
BOOST_CHECK_SMALL(fitted - qOverPt, 1.e-7 * std::max(1., std::abs(qOverPt)));
559+
}
560+
}
561+
}
562+
}
563+
}
564+
565+
BOOST_AUTO_TEST_CASE(AllPointCircleRejectsUnconstrainedOrInvalidInputs)
566+
{
567+
std::array<detail::CircleFitPoint, 3> points{{{0., 0., 1.e-6, 0., 1.e-6},
568+
{1., .01, 1.e-6, 0., 1.e-6},
569+
{2., .04, 1.e-6, 0., 1.e-6}}};
570+
BOOST_CHECK(std::isfinite(detail::estimateCircleQOverPt(points, 5.)));
571+
BOOST_CHECK(std::isfinite(detail::estimateCircleQOverPt(points, detail::MinCircleFitBz)));
572+
BOOST_CHECK(!std::isfinite(detail::estimateCircleQOverPt(points, 0.)));
573+
BOOST_CHECK(!std::isfinite(detail::estimateCircleQOverPt({points.data(), 2}, 5.)));
574+
auto invalid = points;
575+
invalid.back() = invalid.front();
576+
BOOST_CHECK(!std::isfinite(detail::estimateCircleQOverPt(invalid, 5.)));
577+
invalid = points;
578+
invalid[1].xx = invalid[1].yy = 0.;
579+
BOOST_CHECK(!std::isfinite(detail::estimateCircleQOverPt(invalid, 5.)));
580+
invalid = points;
581+
invalid[1].x = std::numeric_limits<double>::quiet_NaN();
582+
BOOST_CHECK(!std::isfinite(detail::estimateCircleQOverPt(invalid, 5.)));
583+
}

0 commit comments

Comments
 (0)