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