@@ -41,46 +41,100 @@ namespace detail
4141constexpr float MinCircleFitBz = 0 .01f ; // kG
4242
4343struct CircleFitPoint {
44- double x, y;
45- double xx, xy, yy;
44+ float x, y;
45+ float xx, xy, yy;
4646};
4747
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
48+ // Preserve cancellation in a*b-c*d with two fused multiply-add operations.
49+ inline float circleDifferenceOfProducts (float a, float b, float c, float d)
5250{
53- constexpr double invalid = std::numeric_limits<double >::quiet_NaN ();
54- if (points.size () < 3 || !std::isfinite (bz) || std::abs (bz) < MinCircleFitBz) {
51+ const float cd = c * d;
52+ return std::fma (a, b, -cd) + std::fma (-c, d, cd);
53+ }
54+
55+ struct CircleFloatDifference {
56+ float hi, lo;
57+ };
58+
59+ // Return the rounded difference and its residual; do not reassociate these sums.
60+ inline CircleFloatDifference circleTwoDiff (float a, float b)
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.
71+ inline 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) {
5575 return invalid;
5676 }
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)) {
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)) {
6184 return invalid;
6285 }
63- const double cs = dx / length, sn = dy / length;
64- std::array<double , 3 > fit{};
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{};
65116 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)) {
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)) {
74123 return invalid;
75124 }
76- const double weight = 1 / variance, basis[3 ] = {1 , x, x * x + y * y};
125+
126+ const float weight = 1 .f / variance, basis[4 ] = {1 .f , point.x , point.r2 , point.y };
77127 for (int i = 0 ; i < 3 ; ++i) {
78- for (int j = 0 ; j < 3 ; ++j) {
79- matrix[i][j] += weight * basis[i] * basis[j];
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]);
80131 }
81- matrix[i][3 ] += weight * basis[i] * y;
82132 }
83133 }
134+
135+ matrix[1 ][0 ] = matrix[0 ][1 ];
136+ matrix[2 ][0 ] = matrix[0 ][2 ];
137+ matrix[2 ][1 ] = matrix[1 ][2 ];
84138 // Solve the three normal equations with partial pivoting.
85139 for (int i = 0 ; i < 3 ; ++i) {
86140 int pivot = i;
@@ -92,28 +146,29 @@ inline double estimateCircleQOverPt(gsl::span<const CircleFitPoint> points, doub
92146 for (int k = i; k < 4 ; ++k) {
93147 std::swap (matrix[i][k], matrix[pivot][k]);
94148 }
95- const double diagonal = matrix[i][i];
96- if (std::abs (diagonal) < 1 .e -15 ) {
149+ const float diagonal = matrix[i][i];
150+ if (std::abs (diagonal) < 1 .e -15f ) {
97151 return invalid;
98152 }
99153 for (int k = i; k < 4 ; ++k) {
100154 matrix[i][k] /= diagonal;
101155 }
102156 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- }
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]);
108163 }
109164 }
110165 }
111166 for (int i = 0 ; i < 3 ; ++i) {
112167 fit[i] = matrix[i][3 ];
113168 }
114169 }
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;
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;
117172}
118173
119174struct RefitMeasurementSlot {
0 commit comments