Skip to content

Commit 3820a39

Browse files
committed
Use fma to improve even further numerical stability
1 parent c0d6334 commit 3820a39

2 files changed

Lines changed: 95 additions & 26 deletions

File tree

‎Detectors/ITSMFT/common/tracking/src/Propagator.cxx‎

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -396,47 +396,49 @@ template <typename State>
396396
bool propagateHelixWithJacobian(State& state, float targetZ, float bz, DenseMatrix5& jacobian) noexcept
397397
{
398398
identity(jacobian);
399-
const double dz = static_cast<double>(targetZ) - state.referenceCoordinate;
400-
if (dz == 0.) {
399+
const float dz = targetZ - state.referenceCoordinate;
400+
if (dz == 0.f) {
401401
return true;
402402
}
403-
const double tanl = state.parameters[3];
404-
const double inverseQPt = state.parameters[4];
405-
if (tanl == 0. || bz == 0.f || inverseQPt == 0.) {
403+
const float tanl = state.parameters[3];
404+
const float inverseQPt = state.parameters[4];
405+
if (tanl == 0.f || bz == 0.f || inverseQPt == 0.f) {
406406
return false;
407407
}
408-
const double n = dz / tanl;
409-
const double curvatureScale = -std::abs(static_cast<double>(o2::constants::math::B2C)) * bz;
410-
const double halfAnglePerQPt = 0.5 * curvatureScale * n;
411-
const double halfAngle = inverseQPt * halfAnglePerQPt;
412-
double sinc, sincDerivative;
413-
if (std::abs(halfAngle) < 0.01) {
408+
const float n = dz / tanl;
409+
const float curvatureScale = -std::abs(o2::constants::math::B2C) * bz;
410+
const float halfAnglePerQPt = 0.5f * curvatureScale * n;
411+
const float halfAngle = inverseQPt * halfAnglePerQPt;
412+
float sinc, sincDerivative;
413+
if (std::abs(halfAngle) < 0.25f) {
414414
// sin(h)/h and its derivative, including their limits at h = 0.
415-
const double h2 = halfAngle * halfAngle;
416-
sinc = 1. + h2 * (-1. / 6. + h2 * (1. / 120. - h2 / 5040.));
417-
sincDerivative = halfAngle * (-1. / 3. + h2 * (1. / 30. - h2 / 840.));
415+
// Keep the cancellation-prone derivative quotient away from small h.
416+
// At |h| <= 0.25 the omitted terms are below float precision.
417+
const float h2 = halfAngle * halfAngle;
418+
sinc = std::fma(h2, std::fma(h2, std::fma(h2, -1.f / 5040.f, 1.f / 120.f), -1.f / 6.f), 1.f);
419+
sincDerivative = halfAngle * std::fma(h2, std::fma(h2, -1.f / 840.f, 1.f / 30.f), -1.f / 3.f);
418420
} else {
419421
sinc = std::sin(halfAngle) / halfAngle;
420422
sincDerivative = (std::cos(halfAngle) - sinc) / halfAngle;
421423
}
422-
const double phi = state.parameters[2];
423-
const double sinMid = std::sin(phi + halfAngle);
424-
const double cosMid = std::cos(phi + halfAngle);
425-
const double endPhi = phi + 2. * halfAngle;
426-
const double dx = n * sinc * cosMid;
427-
const double dy = n * sinc * sinMid;
424+
const float phi = state.parameters[2];
425+
const float sinMid = std::sin(phi + halfAngle);
426+
const float cosMid = std::cos(phi + halfAngle);
427+
const float endPhi = phi + 2.f * halfAngle;
428+
const float dx = n * sinc * cosMid;
429+
const float dy = n * sinc * sinMid;
428430

429431
jacobian[0][2] = -dy;
430432
jacobian[1][2] = dx;
431433
jacobian[0][3] = -n / tanl * std::cos(endPhi);
432434
jacobian[1][3] = -n / tanl * std::sin(endPhi);
433-
jacobian[0][4] = n * halfAnglePerQPt * (sincDerivative * cosMid - sinc * sinMid);
434-
jacobian[1][4] = n * halfAnglePerQPt * (sincDerivative * sinMid + sinc * cosMid);
435-
jacobian[2][3] = -2. * halfAngle / tanl;
436-
jacobian[2][4] = 2. * halfAnglePerQPt;
435+
jacobian[0][4] = n * halfAnglePerQPt * std::fma(sincDerivative, cosMid, -sinc * sinMid);
436+
jacobian[1][4] = n * halfAnglePerQPt * std::fma(sincDerivative, sinMid, sinc * cosMid);
437+
jacobian[2][3] = -2.f * halfAngle / tanl;
438+
jacobian[2][4] = 2.f * halfAnglePerQPt;
437439

438-
state.parameters[0] += dx;
439-
state.parameters[1] += dy;
440+
state.parameters[0] = std::fma(n * sinc, cosMid, state.parameters[0]);
441+
state.parameters[1] = std::fma(n * sinc, sinMid, state.parameters[1]);
440442
state.parameters[2] = endPhi;
441443
state.referenceCoordinate = targetZ;
442444
return true;

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

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,73 @@ BOOST_AUTO_TEST_CASE(ForwardHelixSmallAngleMomentumDerivative)
299299
}
300300
}
301301

302+
BOOST_AUTO_TEST_CASE(ForwardHelixFloatSeriesBoundary)
303+
{
304+
// Isolate the q/pT Jacobian column with a unit momentum variance. An
305+
// independent double-precision trajectory supplies numerical derivatives;
306+
// checking only total position variances can hide this column's cancellation.
307+
for (const float bz : {-5.f, 5.f}) {
308+
for (const float halfAngle : {-0.5f, -0.2501f, -0.25f, -0.2499f, 0.2499f, 0.25f, 0.2501f, 0.5f}) {
309+
for (const float dz : {-32.f, 32.f}) {
310+
for (const float tanl : {-2.5f, 2.5f}) {
311+
const float qOverPt = halfAngle / (0.5f * o2::constants::math::B2C * bz * dz / tanl);
312+
BOOST_TEST_CONTEXT("bz=" << bz << " q/pT=" << qOverPt << " dz=" << dz << " tanl=" << tanl)
313+
{
314+
auto source = diskState();
315+
source.parameters[0] = source.parameters[1] = 0.f;
316+
source.parameters[2] = 0.7f;
317+
source.parameters[3] = tanl;
318+
source.parameters[4] = qOverPt;
319+
std::fill(std::begin(source.covariance), std::end(source.covariance), 0.f);
320+
source.covariance[packedCovarianceIndex(4, 4)] = 1.f;
321+
auto plane = source;
322+
plane.referenceCoordinate += dz;
323+
std::array<double, 5> parameters{};
324+
std::copy(std::begin(source.parameters), std::end(source.parameters), parameters.begin());
325+
const auto expected = intersectConversionPlane(source, parameters, plane, bz);
326+
constexpr double step = 1.e-3;
327+
auto plus = parameters, minus = parameters;
328+
plus[4] += step;
329+
minus[4] -= step;
330+
const auto high = intersectConversionPlane(source, plus, plane, bz);
331+
const auto low = intersectConversionPlane(source, minus, plane, bz);
332+
std::array<double, 5> derivative{};
333+
for (int row = 0; row < 5; ++row) {
334+
derivative[row] = (high[row] - low[row]) / (2. * step);
335+
}
336+
337+
auto direct = source;
338+
auto referenced = source;
339+
SurfaceTrackParameters reference{source};
340+
BOOST_REQUIRE(Propagator::propagateForward(direct, plane.referenceCoordinate, bz));
341+
BOOST_REQUIRE(Propagator::propagateForward(referenced, reference, plane.referenceCoordinate, bz));
342+
for (int row = 0; row < 5; ++row) {
343+
// Bound rounding by the operands rather than a possibly cancelling
344+
// final angle/component. The covariance/Jacobian tolerance below
345+
// remains unchanged from the small-angle regression.
346+
const double path = double(dz) / tanl;
347+
const double scale = row < 2 ? std::abs(path)
348+
: row == 2 ? std::abs(parameters[2]) + 2. * std::abs(double(halfAngle))
349+
: std::abs(expected[row]);
350+
const double positionTolerance = 4. * std::numeric_limits<float>::epsilon() * scale + 1.e-12;
351+
BOOST_CHECK_SMALL(double(direct.parameters[row]) - expected[row], positionTolerance);
352+
BOOST_CHECK_SMALL(double(referenced.parameters[row]) - expected[row], positionTolerance);
353+
BOOST_CHECK_SMALL(double(reference.parameters[row]) - expected[row], positionTolerance);
354+
for (int column = 0; column <= row; ++column) {
355+
const auto index = packedCovarianceIndex(row, column);
356+
const double covariance = derivative[row] * derivative[column];
357+
const double tolerance = 2.e-5 * std::abs(covariance) + 1.e-16;
358+
BOOST_CHECK_SMALL(double(direct.covariance[index]) - covariance, tolerance);
359+
BOOST_CHECK_SMALL(double(referenced.covariance[index]) - covariance, tolerance);
360+
}
361+
}
362+
}
363+
}
364+
}
365+
}
366+
}
367+
}
368+
302369
BOOST_AUTO_TEST_CASE(ForwardHelixTransportMatchesNumericalDerivatives)
303370
{
304371
for (const float bz : {-5.f, 5.f}) {

0 commit comments

Comments
 (0)