File indexing completed on 2025-08-06 08:11:25
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include <boost/test/data/test_case.hpp>
0010 #include <boost/test/unit_test.hpp>
0011
0012 #include "Acts/Definitions/Algebra.hpp"
0013 #include "Acts/Definitions/TrackParametrization.hpp"
0014 #include "Acts/EventData/ParticleHypothesis.hpp"
0015 #include "Acts/EventData/TrackParameters.hpp"
0016 #include "Acts/EventData/TransformationHelpers.hpp"
0017 #include "Acts/EventData/detail/CorrectedTransformationFreeToBound.hpp"
0018 #include "Acts/Geometry/GeometryContext.hpp"
0019 #include "Acts/MagneticField/ConstantBField.hpp"
0020 #include "Acts/MagneticField/MagneticFieldContext.hpp"
0021 #include "Acts/Propagator/EigenStepper.hpp"
0022 #include "Acts/Propagator/Propagator.hpp"
0023 #include "Acts/Propagator/VoidNavigator.hpp"
0024 #include "Acts/Propagator/detail/CovarianceEngine.hpp"
0025 #include "Acts/Surfaces/PerigeeSurface.hpp"
0026 #include "Acts/Surfaces/PlaneSurface.hpp"
0027 #include "Acts/Surfaces/Surface.hpp"
0028 #include "Acts/Tests/CommonHelpers/FloatComparisons.hpp"
0029 #include "Acts/Utilities/Result.hpp"
0030
0031 #include <cmath>
0032 #include <memory>
0033 #include <optional>
0034 #include <random>
0035 #include <tuple>
0036 #include <utility>
0037
0038 namespace bdata = boost::unit_test::data;
0039
0040 namespace Acts::Test {
0041
0042 Acts::GeometryContext gctx;
0043 Acts::MagneticFieldContext mctx;
0044
0045 using namespace Acts::UnitLiterals;
0046
0047 using Covariance = BoundSquareMatrix;
0048 using Jacobian = BoundMatrix;
0049
0050
0051
0052
0053 BOOST_AUTO_TEST_CASE(covariance_engine_test) {
0054
0055 GeometryContext tgContext = GeometryContext();
0056
0057 auto particleHypothesis = ParticleHypothesis::pion();
0058
0059
0060 Vector3 position{1., 2., 3.};
0061 double time = 4.;
0062 Vector3 direction{sqrt(5. / 22.), 3. * sqrt(2. / 55.), 7. / sqrt(110.)};
0063 double qop = 0.125;
0064 FreeVector parameters, startParameters;
0065 parameters << position[0], position[1], position[2], time, direction[0],
0066 direction[1], direction[2], qop;
0067 startParameters = parameters;
0068
0069
0070 Covariance covariance = Covariance::Identity();
0071 Jacobian jacobian = 2. * Jacobian::Identity();
0072 FreeMatrix transportJacobian = 3. * FreeMatrix::Identity();
0073 FreeVector derivatives;
0074 derivatives << 9., 10., 11., 12., 13., 14., 15., 16.;
0075 BoundToFreeMatrix boundToFreeJacobian = 4. * BoundToFreeMatrix::Identity();
0076
0077
0078 detail::transportCovarianceToCurvilinear(covariance, jacobian,
0079 transportJacobian, derivatives,
0080 boundToFreeJacobian, direction);
0081
0082
0083 BOOST_CHECK_NE(covariance, Covariance::Identity());
0084 BOOST_CHECK_NE(jacobian, 2. * Jacobian::Identity());
0085 BOOST_CHECK_EQUAL(transportJacobian, FreeMatrix::Identity());
0086 BOOST_CHECK_EQUAL(derivatives, FreeVector::Zero());
0087 BOOST_CHECK_NE(boundToFreeJacobian, 4. * BoundToFreeMatrix::Identity());
0088 BOOST_CHECK_EQUAL(
0089 direction, Vector3(sqrt(5. / 22.), 3. * sqrt(2. / 55.), 7. / sqrt(110.)));
0090
0091
0092 covariance = Covariance::Identity();
0093 jacobian = 2. * Jacobian::Identity();
0094 transportJacobian = 3. * FreeMatrix::Identity();
0095 derivatives << 9., 10., 11., 12., 13., 14., 15., 16.;
0096 boundToFreeJacobian = 4. * BoundToFreeMatrix::Identity();
0097
0098
0099 FreeToBoundCorrection freeToBoundCorrection(false);
0100 auto surface = Surface::makeShared<PlaneSurface>(position, direction);
0101 detail::transportCovarianceToBound(
0102 tgContext, *surface, covariance, jacobian, transportJacobian, derivatives,
0103 boundToFreeJacobian, parameters, freeToBoundCorrection);
0104
0105 BOOST_CHECK_NE(covariance, Covariance::Identity());
0106 BOOST_CHECK_NE(jacobian, 2. * Jacobian::Identity());
0107 BOOST_CHECK_EQUAL(transportJacobian, FreeMatrix::Identity());
0108 BOOST_CHECK_EQUAL(derivatives, FreeVector::Zero());
0109 BOOST_CHECK_NE(boundToFreeJacobian, 4. * BoundToFreeMatrix::Identity());
0110 BOOST_CHECK_EQUAL(parameters, startParameters);
0111
0112
0113 auto curvResult = detail::curvilinearState(
0114 covariance, jacobian, transportJacobian, derivatives, boundToFreeJacobian,
0115 parameters, particleHypothesis, false, 1337.);
0116 BOOST_CHECK(!std::get<0>(curvResult).covariance().has_value());
0117 BOOST_CHECK_EQUAL(std::get<2>(curvResult), 1337.);
0118
0119
0120 covariance = Covariance::Identity();
0121 jacobian = 2. * Jacobian::Identity();
0122 transportJacobian = 3. * FreeMatrix::Identity();
0123 derivatives << 9., 10., 11., 12., 13., 14., 15., 16.;
0124 boundToFreeJacobian = 4. * BoundToFreeMatrix::Identity();
0125
0126
0127 curvResult = detail::curvilinearState(
0128 covariance, jacobian, transportJacobian, derivatives, boundToFreeJacobian,
0129 parameters, particleHypothesis, true, 1337.);
0130 BOOST_CHECK(std::get<0>(curvResult).covariance().has_value());
0131 BOOST_CHECK_NE(*(std::get<0>(curvResult).covariance()),
0132 Covariance::Identity());
0133 BOOST_CHECK_NE(std::get<1>(curvResult), 2. * Jacobian::Identity());
0134 BOOST_CHECK_EQUAL(std::get<2>(curvResult), 1337.);
0135
0136
0137 auto covarianceBefore = covariance;
0138 auto boundResult =
0139 detail::boundState(tgContext, *surface, covariance, jacobian,
0140 transportJacobian, derivatives, boundToFreeJacobian,
0141 parameters, particleHypothesis, false, 1337.,
0142 freeToBoundCorrection)
0143 .value();
0144 BOOST_CHECK(std::get<0>(curvResult).covariance().has_value());
0145 BOOST_CHECK_EQUAL(*(std::get<0>(curvResult).covariance()), covarianceBefore);
0146 BOOST_CHECK_EQUAL(std::get<2>(boundResult), 1337.);
0147
0148
0149 covariance = Covariance::Identity();
0150 jacobian = 2. * Jacobian::Identity();
0151 transportJacobian = 3. * FreeMatrix::Identity();
0152 derivatives << 9., 10., 11., 12., 13., 14., 15., 16.;
0153 boundToFreeJacobian = 4. * BoundToFreeMatrix::Identity();
0154
0155
0156 boundResult =
0157 detail::boundState(tgContext, *surface, covariance, jacobian,
0158 transportJacobian, derivatives, boundToFreeJacobian,
0159 parameters, ParticleHypothesis::pion(), true, 1337.,
0160 freeToBoundCorrection)
0161 .value();
0162 BOOST_CHECK(std::get<0>(boundResult).covariance().has_value());
0163 BOOST_CHECK_NE(*(std::get<0>(boundResult).covariance()),
0164 Covariance::Identity());
0165 BOOST_CHECK_NE(std::get<1>(boundResult), 2. * Jacobian::Identity());
0166 BOOST_CHECK_EQUAL(std::get<2>(boundResult), 1337.);
0167
0168
0169 freeToBoundCorrection.apply = true;
0170
0171
0172 boundResult =
0173 detail::boundState(tgContext, *surface, covariance, jacobian,
0174 transportJacobian, derivatives, boundToFreeJacobian,
0175 parameters, ParticleHypothesis::pion(), true, 1337.,
0176 freeToBoundCorrection)
0177 .value();
0178 BOOST_CHECK(std::get<0>(boundResult).covariance().has_value());
0179 BOOST_CHECK_NE(*(std::get<0>(boundResult).covariance()),
0180 Covariance::Identity());
0181 }
0182
0183 std::pair<BoundVector, BoundMatrix> boundToBound(const BoundVector& parIn,
0184 const BoundMatrix& covIn,
0185 const Surface& srfA,
0186 const Surface& srfB,
0187 const Vector3& bField) {
0188 Acts::BoundTrackParameters boundParamIn{srfA.getSharedPtr(), parIn, covIn,
0189 ParticleHypothesis::pion()};
0190
0191 auto converted =
0192 detail::boundToBoundConversion(gctx, boundParamIn, srfB, bField).value();
0193
0194 return {converted.parameters(), converted.covariance().value()};
0195 }
0196
0197 using propagator_t = Propagator<EigenStepper<>, VoidNavigator>;
0198
0199 BoundVector localToLocal(const propagator_t& prop, const BoundVector& local,
0200 const Surface& src, const Surface& dst) {
0201 PropagatorOptions<> options{gctx, mctx};
0202 options.stepTolerance = 1e-10;
0203 options.surfaceTolerance = 1e-10;
0204
0205 BoundTrackParameters start{src.getSharedPtr(), local, std::nullopt,
0206 ParticleHypothesis::pion()};
0207
0208 auto res = prop.propagate(start, dst, options).value();
0209 auto endParameters = res.endParameters.value();
0210
0211 BOOST_CHECK_EQUAL(&endParameters.referenceSurface(), &dst);
0212
0213 BoundVector out = endParameters.parameters();
0214 out[eBoundTime] = local[eBoundTime];
0215 return out;
0216 }
0217
0218 propagator_t makePropagator(const Vector3& bField) {
0219 return propagator_t{EigenStepper<>{std::make_shared<ConstantBField>(bField)},
0220 VoidNavigator{}};
0221 }
0222
0223 BoundMatrix numericalBoundToBoundJacobian(const propagator_t& prop,
0224 const BoundVector& parA,
0225 const Surface& srfA,
0226 const Surface& srfB) {
0227 double h = 1e-4;
0228 BoundMatrix J;
0229 for (std::size_t i = 0; i < 6; i++) {
0230 for (std::size_t j = 0; j < 6; j++) {
0231 BoundVector parInitial1 = parA;
0232 BoundVector parInitial2 = parA;
0233 parInitial1[i] -= h;
0234 parInitial2[i] += h;
0235 BoundVector parFinal1 = localToLocal(prop, parInitial1, srfA, srfB);
0236 BoundVector parFinal2 = localToLocal(prop, parInitial2, srfA, srfB);
0237
0238 J(j, i) = (parFinal2[j] - parFinal1[j]) / (2 * h);
0239 }
0240 }
0241 return J;
0242 }
0243
0244 unsigned int getNextSeed() {
0245 static unsigned int seed = 10;
0246 return ++seed;
0247 }
0248
0249 auto makeDist(double a, double b) {
0250 return bdata::random(
0251 (bdata::engine = std::mt19937{}, bdata::seed = getNextSeed(),
0252 bdata::distribution = std::uniform_real_distribution<double>(a, b)));
0253 }
0254
0255 const auto locDist = makeDist(-5_mm, 5_mm);
0256 const auto bFieldDist = makeDist(0, 3_T);
0257 const auto angleDist = makeDist(-2 * M_PI, 2 * M_PI);
0258 const auto posDist = makeDist(-50_mm, 50_mm);
0259
0260 #define MAKE_SURFACE() \
0261 [&]() { \
0262 Transform3 transformA = Transform3::Identity(); \
0263 transformA = AngleAxis3(Rx, Vector3::UnitX()); \
0264 transformA = AngleAxis3(Ry, Vector3::UnitY()); \
0265 transformA = AngleAxis3(Rz, Vector3::UnitZ()); \
0266 transformA.translation() << gx, gy, gz; \
0267 return Surface::makeShared<PlaneSurface>(transformA); \
0268 }()
0269
0270 BOOST_DATA_TEST_CASE(CovarianceConversionSamePlane,
0271 (bFieldDist ^ bFieldDist ^ bFieldDist ^ angleDist ^
0272 angleDist ^ angleDist ^ posDist ^ posDist ^ posDist ^
0273 locDist ^ locDist) ^
0274 bdata::xrange(100),
0275 Bx, By, Bz, Rx, Ry, Rz, gx, gy, gz, l0, l1, index) {
0276 (void)index;
0277 const Vector3 bField{Bx, By, Bz};
0278
0279 auto planeSurfaceA = MAKE_SURFACE();
0280 auto planeSurfaceB =
0281 Surface::makeShared<PlaneSurface>(planeSurfaceA->transform(gctx));
0282
0283 BoundMatrix covA;
0284 covA.setZero();
0285 covA.diagonal() << 1, 2, 3, 4, 5, 6;
0286
0287 BoundVector parA;
0288 parA << l0, l1, M_PI / 4., M_PI_2 * 0.9, -1 / 1_GeV, 5_ns;
0289
0290
0291 auto [parB, covB] =
0292 boundToBound(parA, covA, *planeSurfaceA, *planeSurfaceB, bField);
0293
0294
0295 CHECK_CLOSE_ABS(parA, parB, 1e-9);
0296 CHECK_CLOSE_COVARIANCE(covA, covB, 1e-9);
0297
0298
0299 auto [parA2, covA2] =
0300 boundToBound(parB, covB, *planeSurfaceB, *planeSurfaceA, bField);
0301 CHECK_CLOSE_ABS(parA, parA2, 1e-9);
0302 CHECK_CLOSE_COVARIANCE(covA, covA2, 1e-9);
0303
0304 auto prop = makePropagator(bField);
0305
0306 BoundMatrix J =
0307 numericalBoundToBoundJacobian(prop, parA, *planeSurfaceA, *planeSurfaceB);
0308 BoundMatrix covC = J * covA * J.transpose();
0309 CHECK_CLOSE_COVARIANCE(covB, covC, 1e-6);
0310 }
0311
0312 BOOST_DATA_TEST_CASE(CovarianceConversionRotatedPlane,
0313 (bFieldDist ^ bFieldDist ^ bFieldDist ^ angleDist ^
0314 angleDist ^ angleDist ^ posDist ^ posDist ^ posDist ^
0315 locDist ^ locDist ^ angleDist) ^
0316 bdata::xrange(100),
0317 Bx, By, Bz, Rx, Ry, Rz, gx, gy, gz, l0, l1, angle, index) {
0318 (void)index;
0319 const Vector3 bField{Bx, By, Bz};
0320
0321 auto planeSurfaceA = MAKE_SURFACE();
0322
0323 Transform3 transform;
0324 transform = planeSurfaceA->transform(gctx).rotation();
0325 transform = AngleAxis3(angle, planeSurfaceA->normal(gctx)) * transform;
0326 transform.translation() = planeSurfaceA->transform(gctx).translation();
0327 auto planeSurfaceB = Surface::makeShared<PlaneSurface>(transform);
0328
0329
0330 CHECK_CLOSE_ABS(planeSurfaceA->normal(gctx), planeSurfaceB->normal(gctx),
0331 1e-9);
0332
0333 BoundMatrix covA;
0334 covA.setZero();
0335 covA.diagonal() << 1, 2, 3, 4, 5, 6;
0336
0337 BoundVector parA;
0338 parA << l0, l1, M_PI / 4., M_PI_2 * 0.9, -1 / 1_GeV, 5_ns;
0339
0340 auto [parB, covB] =
0341 boundToBound(parA, covA, *planeSurfaceA, *planeSurfaceB, bField);
0342 BoundVector exp = parA;
0343
0344 exp.head<2>() = Eigen::Rotation2D<double>(-angle) * parA.head<2>();
0345
0346 CHECK_CLOSE_ABS(exp, parB, 1e-9);
0347
0348
0349 auto [parA2, covA2] =
0350 boundToBound(parB, covB, *planeSurfaceB, *planeSurfaceA, bField);
0351 CHECK_CLOSE_ABS(parA, parA2, 1e-9);
0352 CHECK_CLOSE_COVARIANCE(covA, covA2, 1e-9);
0353
0354 auto prop = makePropagator(bField);
0355 BoundMatrix J =
0356 numericalBoundToBoundJacobian(prop, parA, *planeSurfaceA, *planeSurfaceB);
0357 BoundMatrix covC = J * covA * J.transpose();
0358 CHECK_CLOSE_COVARIANCE(covB, covC, 1e-6);
0359 }
0360
0361 BOOST_DATA_TEST_CASE(CovarianceConversionL0TiltedPlane,
0362 (bFieldDist ^ bFieldDist ^ bFieldDist ^ angleDist ^
0363 angleDist ^ angleDist ^ posDist ^ posDist ^ posDist ^
0364 locDist ^ angleDist) ^
0365 bdata::xrange(100),
0366 Bx, By, Bz, Rx, Ry, Rz, gx, gy, gz, l1, angle, index) {
0367 (void)index;
0368 const Vector3 bField{Bx, By, Bz};
0369
0370 auto planeSurfaceA = MAKE_SURFACE();
0371
0372
0373 Transform3 transform;
0374 transform = planeSurfaceA->transform(gctx).rotation();
0375
0376
0377 Vector3 axis = planeSurfaceA->transform(gctx).rotation() * Vector3::UnitY();
0378 transform = AngleAxis3(angle, axis) * transform;
0379
0380 transform.translation() = planeSurfaceA->transform(gctx).translation();
0381
0382 auto planeSurfaceB = Surface::makeShared<PlaneSurface>(transform);
0383
0384 BoundVector parA;
0385
0386 parA << 0, l1, M_PI / 4., M_PI_2 * 0.9, -1 / 1_GeV, 5_ns;
0387
0388 BoundMatrix covA;
0389 covA.setZero();
0390 covA.diagonal() << 1, 2, 3, 4, 5, 6;
0391
0392 auto [parB, covB] =
0393 boundToBound(parA, covA, *planeSurfaceA, *planeSurfaceB, bField);
0394
0395
0396 auto [parA2, covA2] =
0397 boundToBound(parB, covB, *planeSurfaceB, *planeSurfaceA, bField);
0398 CHECK_CLOSE_ABS(parA, parA2, 1e-9);
0399 CHECK_CLOSE_COVARIANCE(covA, covA2, 1e-7);
0400
0401 auto prop = makePropagator(bField);
0402 BoundMatrix J =
0403 numericalBoundToBoundJacobian(prop, parA, *planeSurfaceA, *planeSurfaceB);
0404 BoundMatrix covC = J * covA * J.transpose();
0405 CHECK_CLOSE_OR_SMALL((covB.template topLeftCorner<2, 2>()),
0406 (covC.template topLeftCorner<2, 2>()), 1e-7, 1e-9);
0407 CHECK_CLOSE_OR_SMALL(covB.diagonal(), covC.diagonal(), 1e-7, 1e-9);
0408 }
0409
0410 BOOST_DATA_TEST_CASE(CovarianceConversionL1TiltedPlane,
0411 (bFieldDist ^ bFieldDist ^ bFieldDist ^ angleDist ^
0412 angleDist ^ angleDist ^ posDist ^ posDist ^ posDist ^
0413 locDist ^ angleDist) ^
0414 bdata::xrange(100),
0415 Bx, By, Bz, Rx, Ry, Rz, gx, gy, gz, l0, angle, index) {
0416 (void)index;
0417 const Vector3 bField{Bx, By, Bz};
0418
0419 auto planeSurfaceA = MAKE_SURFACE();
0420
0421
0422 Transform3 transform;
0423 transform = planeSurfaceA->transform(gctx).rotation();
0424
0425 Vector3 axis = planeSurfaceA->transform(gctx).rotation() * Vector3::UnitX();
0426 transform = AngleAxis3(angle, axis) * transform;
0427
0428 transform.translation() = planeSurfaceA->transform(gctx).translation();
0429
0430 auto planeSurfaceB = Surface::makeShared<PlaneSurface>(transform);
0431
0432 BoundVector parA;
0433
0434 parA << l0, 0, M_PI / 4., M_PI_2 * 0.9, -1 / 1_GeV, 5_ns;
0435
0436 BoundMatrix covA;
0437 covA.setZero();
0438 covA.diagonal() << 1, 2, 3, 4, 5, 6;
0439
0440 auto [parB, covB] =
0441 boundToBound(parA, covA, *planeSurfaceA, *planeSurfaceB, bField);
0442
0443
0444 auto [parA2, covA2] =
0445 boundToBound(parB, covB, *planeSurfaceB, *planeSurfaceA, bField);
0446 CHECK_CLOSE_ABS(parA, parA2, 1e-9);
0447
0448 CHECK_CLOSE_COVARIANCE(covA, covA2, 1e-6);
0449
0450 auto prop = makePropagator(bField);
0451 BoundMatrix J =
0452 numericalBoundToBoundJacobian(prop, parA, *planeSurfaceA, *planeSurfaceB);
0453 BoundMatrix covC = J * covA * J.transpose();
0454 CHECK_CLOSE_OR_SMALL((covB.template topLeftCorner<2, 2>()),
0455 (covC.template topLeftCorner<2, 2>()), 1e-6, 1e-9);
0456 CHECK_CLOSE_OR_SMALL(covB.diagonal(), covC.diagonal(), 1e-6, 1e-9);
0457 }
0458
0459 BOOST_DATA_TEST_CASE(CovarianceConversionPerigee,
0460 (bFieldDist ^ bFieldDist ^ bFieldDist ^ angleDist ^
0461 angleDist ^ angleDist ^ posDist ^ posDist ^ posDist ^
0462 locDist ^ locDist ^ angleDist ^ angleDist ^ angleDist) ^
0463 bdata::xrange(100),
0464 Bx, By, Bz, Rx, Ry, Rz, gx, gy, gz, l0, l1, pRx, pRy, pRz,
0465 index) {
0466 (void)index;
0467 const Vector3 bField{Bx, By, Bz};
0468
0469 auto planeSurfaceA = MAKE_SURFACE();
0470
0471 BoundVector parA;
0472 parA << l0, l1, M_PI / 4., M_PI_2 * 0.9, -1 / 1_GeV, 5_ns;
0473
0474 BoundMatrix covA;
0475 covA.setZero();
0476 covA.diagonal() << 1, 2, 3, 4, 5, 6;
0477
0478 Vector3 global = planeSurfaceA->localToGlobal(gctx, parA.head<2>());
0479
0480 Transform3 transform;
0481 transform.setIdentity();
0482 transform.rotate(AngleAxis3(pRx, Vector3::UnitX()));
0483 transform.rotate(AngleAxis3(pRy, Vector3::UnitY()));
0484 transform.rotate(AngleAxis3(pRz, Vector3::UnitZ()));
0485 transform.translation() = global;
0486
0487 auto perigee = Surface::makeShared<PerigeeSurface>(transform);
0488
0489 auto [parB, covB] =
0490 boundToBound(parA, covA, *planeSurfaceA, *perigee, bField);
0491
0492
0493 auto [parA2, covA2] =
0494 boundToBound(parB, covB, *perigee, *planeSurfaceA, bField);
0495 CHECK_CLOSE_ABS(parA, parA2, 1e-9);
0496 CHECK_CLOSE_COVARIANCE(covA, covA2, 1e-9);
0497
0498 auto prop = makePropagator(bField);
0499 BoundMatrix J =
0500 numericalBoundToBoundJacobian(prop, parA, *planeSurfaceA, *perigee);
0501 BoundMatrix covC = J * covA * J.transpose();
0502 CHECK_CLOSE_ABS(covB, covC, 1e-7);
0503 }
0504
0505 }