Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-07-09 07:51:10

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
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 <numbers>
0034 #include <optional>
0035 #include <random>
0036 #include <tuple>
0037 #include <utility>
0038 
0039 namespace bdata = boost::unit_test::data;
0040 
0041 namespace Acts::Test {
0042 
0043 Acts::GeometryContext gctx;
0044 Acts::MagneticFieldContext mctx;
0045 
0046 using namespace Acts::UnitLiterals;
0047 
0048 using Covariance = BoundSquareMatrix;
0049 using Jacobian = BoundMatrix;
0050 
0051 /// These tests do not test for a correct covariance transport but only for the
0052 /// correct conservation or modification of certain variables. A test suite for
0053 /// the numerical correctness is performed in the integration tests.
0054 BOOST_AUTO_TEST_CASE(covariance_engine_test) {
0055   // Create a test context
0056   GeometryContext tgContext = GeometryContext();
0057 
0058   auto particleHypothesis = ParticleHypothesis::pion();
0059 
0060   // Build a start vector
0061   Vector3 position{1., 2., 3.};
0062   double time = 4.;
0063   Vector3 direction{sqrt(5. / 22.), 3. * sqrt(2. / 55.), 7. / 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   // Build covariance matrix, jacobians and related components
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   // Covariance transport to curvilinear coordinates
0080   detail::transportCovarianceToCurvilinear(
0081       covariance, jacobian, transportJacobian, derivatives, boundToFreeJacobian,
0082       additionalFreeCovariance, direction);
0083 
0084   // Tests to see that the right components are (un-)changed
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(
0091       direction, Vector3(sqrt(5. / 22.), 3. * sqrt(2. / 55.), 7. / sqrt(110.)));
0092 
0093   // Reset
0094   covariance = Covariance::Identity();
0095   jacobian = 2. * Jacobian::Identity();
0096   transportJacobian = 3. * FreeMatrix::Identity();
0097   derivatives << 9., 10., 11., 12., 13., 14., 15., 16.;
0098   boundToFreeJacobian = 4. * BoundToFreeMatrix::Identity();
0099 
0100   // Repeat transport to surface
0101   FreeToBoundCorrection freeToBoundCorrection(false);
0102   std::shared_ptr<PlaneSurface> surface =
0103       CurvilinearSurface(position, direction).planeSurface();
0104   detail::transportCovarianceToBound(
0105       tgContext, *surface, covariance, jacobian, transportJacobian, derivatives,
0106       boundToFreeJacobian, additionalFreeCovariance, parameters,
0107       freeToBoundCorrection);
0108 
0109   BOOST_CHECK_NE(covariance, Covariance::Identity());
0110   BOOST_CHECK_NE(jacobian, 2. * Jacobian::Identity());
0111   BOOST_CHECK_EQUAL(transportJacobian, FreeMatrix::Identity());
0112   BOOST_CHECK_EQUAL(derivatives, FreeVector::Zero());
0113   BOOST_CHECK_NE(boundToFreeJacobian, 4. * BoundToFreeMatrix::Identity());
0114   BOOST_CHECK_EQUAL(parameters, startParameters);
0115 
0116   // Produce a curvilinear state without covariance matrix
0117   auto curvResult = detail::curvilinearState(
0118       covariance, jacobian, transportJacobian, derivatives, boundToFreeJacobian,
0119       std::nullopt, parameters, particleHypothesis, false, 1337.);
0120   BOOST_CHECK(!std::get<0>(curvResult).covariance().has_value());
0121   BOOST_CHECK_EQUAL(std::get<2>(curvResult), 1337.);
0122 
0123   // Reset
0124   covariance = Covariance::Identity();
0125   jacobian = 2. * Jacobian::Identity();
0126   transportJacobian = 3. * FreeMatrix::Identity();
0127   derivatives << 9., 10., 11., 12., 13., 14., 15., 16.;
0128   boundToFreeJacobian = 4. * BoundToFreeMatrix::Identity();
0129 
0130   // Produce a curvilinear state with covariance matrix
0131   curvResult = detail::curvilinearState(
0132       covariance, jacobian, transportJacobian, derivatives, boundToFreeJacobian,
0133       additionalFreeCovariance, parameters, particleHypothesis, true, 1337.);
0134   BOOST_CHECK(std::get<0>(curvResult).covariance().has_value());
0135   BOOST_CHECK_NE(*(std::get<0>(curvResult).covariance()),
0136                  Covariance::Identity());
0137   BOOST_CHECK_NE(std::get<1>(curvResult), 2. * Jacobian::Identity());
0138   BOOST_CHECK_EQUAL(std::get<2>(curvResult), 1337.);
0139 
0140   // Produce a bound state without covariance matrix
0141   auto covarianceBefore = covariance;
0142   auto boundResult =
0143       detail::boundState(
0144           tgContext, *surface, covariance, jacobian, transportJacobian,
0145           derivatives, boundToFreeJacobian, additionalFreeCovariance,
0146           parameters, particleHypothesis, false, 1337., freeToBoundCorrection)
0147           .value();
0148   BOOST_CHECK(std::get<0>(curvResult).covariance().has_value());
0149   BOOST_CHECK_EQUAL(*(std::get<0>(curvResult).covariance()), covarianceBefore);
0150   BOOST_CHECK_EQUAL(std::get<2>(boundResult), 1337.);
0151 
0152   // Reset
0153   covariance = Covariance::Identity();
0154   jacobian = 2. * Jacobian::Identity();
0155   transportJacobian = 3. * FreeMatrix::Identity();
0156   derivatives << 9., 10., 11., 12., 13., 14., 15., 16.;
0157   boundToFreeJacobian = 4. * BoundToFreeMatrix::Identity();
0158 
0159   // Produce a bound state with covariance matrix
0160   boundResult =
0161       detail::boundState(tgContext, *surface, covariance, jacobian,
0162                          transportJacobian, derivatives, boundToFreeJacobian,
0163                          additionalFreeCovariance, parameters,
0164                          ParticleHypothesis::pion(), true, 1337.,
0165                          freeToBoundCorrection)
0166           .value();
0167   BOOST_CHECK(std::get<0>(boundResult).covariance().has_value());
0168   BOOST_CHECK_NE(*(std::get<0>(boundResult).covariance()),
0169                  Covariance::Identity());
0170   BOOST_CHECK_NE(std::get<1>(boundResult), 2. * Jacobian::Identity());
0171   BOOST_CHECK_EQUAL(std::get<2>(boundResult), 1337.);
0172 
0173   // Reset
0174   freeToBoundCorrection.apply = true;
0175 
0176   // Produce a bound state with free to bound correction
0177   boundResult =
0178       detail::boundState(tgContext, *surface, covariance, jacobian,
0179                          transportJacobian, derivatives, boundToFreeJacobian,
0180                          additionalFreeCovariance, parameters,
0181                          ParticleHypothesis::pion(), true, 1337.,
0182                          freeToBoundCorrection)
0183           .value();
0184   BOOST_CHECK(std::get<0>(boundResult).covariance().has_value());
0185   BOOST_CHECK_NE(*(std::get<0>(boundResult).covariance()),
0186                  Covariance::Identity());
0187 }
0188 
0189 std::pair<BoundVector, BoundMatrix> boundToBound(const BoundVector& parIn,
0190                                                  const BoundMatrix& covIn,
0191                                                  const Surface& srfA,
0192                                                  const Surface& srfB,
0193                                                  const Vector3& bField) {
0194   Acts::BoundTrackParameters boundParamIn{srfA.getSharedPtr(), parIn, covIn,
0195                                           ParticleHypothesis::pion()};
0196 
0197   auto converted =
0198       detail::boundToBoundConversion(gctx, boundParamIn, srfB, bField).value();
0199 
0200   return {converted.parameters(), converted.covariance().value()};
0201 }
0202 
0203 using propagator_t = Propagator<EigenStepper<>, VoidNavigator>;
0204 
0205 BoundVector localToLocal(const propagator_t& prop, const BoundVector& local,
0206                          const Surface& src, const Surface& dst) {
0207   using PropagatorOptions = typename propagator_t::template Options<>;
0208   PropagatorOptions options{gctx, mctx};
0209   options.stepping.stepTolerance = 1e-10;
0210   options.surfaceTolerance = 1e-10;
0211 
0212   BoundTrackParameters start{src.getSharedPtr(), local, std::nullopt,
0213                              ParticleHypothesis::pion()};
0214 
0215   auto res = prop.propagate(start, dst, options).value();
0216   auto endParameters = res.endParameters.value();
0217 
0218   BOOST_CHECK_EQUAL(&endParameters.referenceSurface(), &dst);
0219 
0220   BoundVector out = endParameters.parameters();
0221   out[eBoundTime] = local[eBoundTime];
0222   return out;
0223 }
0224 
0225 propagator_t makePropagator(const Vector3& bField) {
0226   return propagator_t{EigenStepper<>{std::make_shared<ConstantBField>(bField)},
0227                       VoidNavigator{}};
0228 }
0229 
0230 BoundMatrix numericalBoundToBoundJacobian(const propagator_t& prop,
0231                                           const BoundVector& parA,
0232                                           const Surface& srfA,
0233                                           const Surface& srfB) {
0234   double h = 1e-4;
0235   BoundMatrix J;
0236   for (std::size_t i = 0; i < 6; i++) {
0237     for (std::size_t j = 0; j < 6; j++) {
0238       BoundVector parInitial1 = parA;
0239       BoundVector parInitial2 = parA;
0240       parInitial1[i] -= h;
0241       parInitial2[i] += h;
0242       BoundVector parFinal1 = localToLocal(prop, parInitial1, srfA, srfB);
0243       BoundVector parFinal2 = localToLocal(prop, parInitial2, srfA, srfB);
0244 
0245       J(j, i) = (parFinal2[j] - parFinal1[j]) / (2 * h);
0246     }
0247   }
0248   return J;
0249 }
0250 
0251 unsigned int getNextSeed() {
0252   static unsigned int seed = 10;
0253   return ++seed;
0254 }
0255 
0256 auto makeDist(double a, double b) {
0257   return bdata::random(
0258       (bdata::engine = std::mt19937{}, bdata::seed = getNextSeed(),
0259        bdata::distribution = std::uniform_real_distribution<double>(a, b)));
0260 }
0261 
0262 const auto locDist = makeDist(-5_mm, 5_mm);
0263 const auto bFieldDist = makeDist(0, 3_T);
0264 const auto angleDist = makeDist(-2 * std::numbers::pi, 2 * std::numbers::pi);
0265 const auto posDist = makeDist(-50_mm, 50_mm);
0266 
0267 #define MAKE_SURFACE()                                    \
0268   [&]() {                                                 \
0269     Transform3 transformA = Transform3::Identity();       \
0270     transformA = AngleAxis3(Rx, Vector3::UnitX());        \
0271     transformA = AngleAxis3(Ry, Vector3::UnitY());        \
0272     transformA = AngleAxis3(Rz, Vector3::UnitZ());        \
0273     transformA.translation() << gx, gy, gz;               \
0274     return Surface::makeShared<PlaneSurface>(transformA); \
0275   }()
0276 
0277 BOOST_DATA_TEST_CASE(CovarianceConversionSamePlane,
0278                      (bFieldDist ^ bFieldDist ^ bFieldDist ^ angleDist ^
0279                       angleDist ^ angleDist ^ posDist ^ posDist ^ posDist ^
0280                       locDist ^ locDist) ^
0281                          bdata::xrange(100),
0282                      Bx, By, Bz, Rx, Ry, Rz, gx, gy, gz, l0, l1, index) {
0283   (void)index;
0284   const Vector3 bField{Bx, By, Bz};
0285 
0286   auto planeSurfaceA = MAKE_SURFACE();
0287   auto planeSurfaceB =
0288       Surface::makeShared<PlaneSurface>(planeSurfaceA->transform(gctx));
0289 
0290   BoundMatrix covA;
0291   covA.setZero();
0292   covA.diagonal() << 1, 2, 3, 4, 5, 6;
0293 
0294   BoundVector parA;
0295   parA << l0, l1, std::numbers::pi / 4., std::numbers::pi / 2. * 0.9,
0296       -1 / 1_GeV, 5_ns;
0297 
0298   // identical surface, this should work
0299   auto [parB, covB] =
0300       boundToBound(parA, covA, *planeSurfaceA, *planeSurfaceB, bField);
0301 
0302   // these should be the same because the plane surface are the same
0303   CHECK_CLOSE_ABS(parA, parB, 1e-9);
0304   CHECK_CLOSE_COVARIANCE(covA, covB, 1e-9);
0305 
0306   // now go back
0307   auto [parA2, covA2] =
0308       boundToBound(parB, covB, *planeSurfaceB, *planeSurfaceA, bField);
0309   CHECK_CLOSE_ABS(parA, parA2, 1e-9);
0310   CHECK_CLOSE_COVARIANCE(covA, covA2, 1e-9);
0311 
0312   auto prop = makePropagator(bField);
0313 
0314   BoundMatrix J =
0315       numericalBoundToBoundJacobian(prop, parA, *planeSurfaceA, *planeSurfaceB);
0316   BoundMatrix covC = J * covA * J.transpose();
0317   CHECK_CLOSE_COVARIANCE(covB, covC, 1e-6);
0318 }
0319 
0320 BOOST_DATA_TEST_CASE(CovarianceConversionRotatedPlane,
0321                      (bFieldDist ^ bFieldDist ^ bFieldDist ^ angleDist ^
0322                       angleDist ^ angleDist ^ posDist ^ posDist ^ posDist ^
0323                       locDist ^ locDist ^ angleDist) ^
0324                          bdata::xrange(100),
0325                      Bx, By, Bz, Rx, Ry, Rz, gx, gy, gz, l0, l1, angle, index) {
0326   (void)index;
0327   const Vector3 bField{Bx, By, Bz};
0328 
0329   auto planeSurfaceA = MAKE_SURFACE();
0330 
0331   Transform3 transform;
0332   transform = planeSurfaceA->transform(gctx).rotation();
0333   transform = AngleAxis3(angle, planeSurfaceA->normal(gctx)) * transform;
0334   transform.translation() = planeSurfaceA->transform(gctx).translation();
0335   auto planeSurfaceB = Surface::makeShared<PlaneSurface>(transform);
0336 
0337   // sanity check that the normal didn't change
0338   CHECK_CLOSE_ABS(planeSurfaceA->normal(gctx), planeSurfaceB->normal(gctx),
0339                   1e-9);
0340 
0341   BoundMatrix covA;
0342   covA.setZero();
0343   covA.diagonal() << 1, 2, 3, 4, 5, 6;
0344 
0345   BoundVector parA;
0346   parA << l0, l1, std::numbers::pi / 4., std::numbers::pi / 2. * 0.9,
0347       -1 / 1_GeV, 5_ns;
0348 
0349   auto [parB, covB] =
0350       boundToBound(parA, covA, *planeSurfaceA, *planeSurfaceB, bField);
0351   BoundVector exp = parA;
0352   // loc0 and loc1 are rotated
0353   exp.head<2>() = Eigen::Rotation2D<double>(-angle) * parA.head<2>();
0354 
0355   CHECK_CLOSE_ABS(exp, parB, 1e-9);
0356 
0357   // now go back
0358   auto [parA2, covA2] =
0359       boundToBound(parB, covB, *planeSurfaceB, *planeSurfaceA, bField);
0360   CHECK_CLOSE_ABS(parA, parA2, 1e-9);
0361   CHECK_CLOSE_COVARIANCE(covA, covA2, 1e-9);
0362 
0363   auto prop = makePropagator(bField);
0364   BoundMatrix J =
0365       numericalBoundToBoundJacobian(prop, parA, *planeSurfaceA, *planeSurfaceB);
0366   BoundMatrix covC = J * covA * J.transpose();
0367   CHECK_CLOSE_COVARIANCE(covB, covC, 1e-6);
0368 }
0369 
0370 BOOST_DATA_TEST_CASE(CovarianceConversionL0TiltedPlane,
0371                      (bFieldDist ^ bFieldDist ^ bFieldDist ^ angleDist ^
0372                       angleDist ^ angleDist ^ posDist ^ posDist ^ posDist ^
0373                       locDist ^ angleDist) ^
0374                          bdata::xrange(100),
0375                      Bx, By, Bz, Rx, Ry, Rz, gx, gy, gz, l1, angle, index) {
0376   (void)index;
0377   const Vector3 bField{Bx, By, Bz};
0378 
0379   auto planeSurfaceA = MAKE_SURFACE();
0380 
0381   // make plane that is slightly rotated
0382   Transform3 transform;
0383   transform = planeSurfaceA->transform(gctx).rotation();
0384 
0385   // figure out rotation axis along local x
0386   Vector3 axis = planeSurfaceA->transform(gctx).rotation() * Vector3::UnitY();
0387   transform = AngleAxis3(angle, axis) * transform;
0388 
0389   transform.translation() = planeSurfaceA->transform(gctx).translation();
0390 
0391   auto planeSurfaceB = Surface::makeShared<PlaneSurface>(transform);
0392 
0393   BoundVector parA;
0394   // loc 0 must be zero so we're on the intersection of both surfaces.
0395   parA << 0, l1, std::numbers::pi / 4., std::numbers::pi / 2. * 0.9, -1 / 1_GeV,
0396       5_ns;
0397 
0398   BoundMatrix covA;
0399   covA.setZero();
0400   covA.diagonal() << 1, 2, 3, 4, 5, 6;
0401 
0402   auto [parB, covB] =
0403       boundToBound(parA, covA, *planeSurfaceA, *planeSurfaceB, bField);
0404 
0405   // now go back
0406   auto [parA2, covA2] =
0407       boundToBound(parB, covB, *planeSurfaceB, *planeSurfaceA, bField);
0408   CHECK_CLOSE_ABS(parA, parA2, 1e-9);
0409   CHECK_CLOSE_COVARIANCE(covA, covA2, 1e-7);
0410 
0411   auto prop = makePropagator(bField);
0412   BoundMatrix J =
0413       numericalBoundToBoundJacobian(prop, parA, *planeSurfaceA, *planeSurfaceB);
0414   BoundMatrix covC = J * covA * J.transpose();
0415   CHECK_CLOSE_OR_SMALL((covB.template topLeftCorner<2, 2>()),
0416                        (covC.template topLeftCorner<2, 2>()), 1e-7, 1e-9);
0417   CHECK_CLOSE_OR_SMALL(covB.diagonal(), covC.diagonal(), 1e-7, 1e-9);
0418 }
0419 
0420 BOOST_DATA_TEST_CASE(CovarianceConversionL1TiltedPlane,
0421                      (bFieldDist ^ bFieldDist ^ bFieldDist ^ angleDist ^
0422                       angleDist ^ angleDist ^ posDist ^ posDist ^ posDist ^
0423                       locDist ^ angleDist) ^
0424                          bdata::xrange(100),
0425                      Bx, By, Bz, Rx, Ry, Rz, gx, gy, gz, l0, angle, index) {
0426   (void)index;
0427   const Vector3 bField{Bx, By, Bz};
0428 
0429   auto planeSurfaceA = MAKE_SURFACE();
0430 
0431   // make plane that is slightly rotated
0432   Transform3 transform;
0433   transform = planeSurfaceA->transform(gctx).rotation();
0434 
0435   Vector3 axis = planeSurfaceA->transform(gctx).rotation() * Vector3::UnitX();
0436   transform = AngleAxis3(angle, axis) * transform;
0437 
0438   transform.translation() = planeSurfaceA->transform(gctx).translation();
0439 
0440   auto planeSurfaceB = Surface::makeShared<PlaneSurface>(transform);
0441 
0442   BoundVector parA;
0443   // loc 1 must be zero so we're on the intersection of both surfaces.
0444   parA << l0, 0, std::numbers::pi / 4., std::numbers::pi / 2. * 0.9, -1 / 1_GeV,
0445       5_ns;
0446 
0447   BoundMatrix covA;
0448   covA.setZero();
0449   covA.diagonal() << 1, 2, 3, 4, 5, 6;
0450 
0451   auto [parB, covB] =
0452       boundToBound(parA, covA, *planeSurfaceA, *planeSurfaceB, bField);
0453 
0454   // now go back
0455   auto [parA2, covA2] =
0456       boundToBound(parB, covB, *planeSurfaceB, *planeSurfaceA, bField);
0457   CHECK_CLOSE_ABS(parA, parA2, 1e-9);
0458   // tolerance is a bit higher here
0459   CHECK_CLOSE_COVARIANCE(covA, covA2, 1e-6);
0460 
0461   auto prop = makePropagator(bField);
0462   BoundMatrix J =
0463       numericalBoundToBoundJacobian(prop, parA, *planeSurfaceA, *planeSurfaceB);
0464   BoundMatrix covC = J * covA * J.transpose();
0465   CHECK_CLOSE_OR_SMALL((covB.template topLeftCorner<2, 2>()),
0466                        (covC.template topLeftCorner<2, 2>()), 1e-6, 1e-9);
0467   CHECK_CLOSE_OR_SMALL(covB.diagonal(), covC.diagonal(), 1e-6, 1e-9);
0468 }
0469 
0470 BOOST_DATA_TEST_CASE(CovarianceConversionPerigee,
0471                      (bFieldDist ^ bFieldDist ^ bFieldDist ^ angleDist ^
0472                       angleDist ^ angleDist ^ posDist ^ posDist ^ posDist ^
0473                       locDist ^ locDist ^ angleDist ^ angleDist ^ angleDist) ^
0474                          bdata::xrange(100),
0475                      Bx, By, Bz, Rx, Ry, Rz, gx, gy, gz, l0, l1, pRx, pRy, pRz,
0476                      index) {
0477   (void)index;
0478   const Vector3 bField{Bx, By, Bz};
0479 
0480   auto planeSurfaceA = MAKE_SURFACE();
0481 
0482   BoundVector parA;
0483   parA << l0, l1, std::numbers::pi / 4., std::numbers::pi / 2. * 0.9,
0484       -1 / 1_GeV, 5_ns;
0485 
0486   BoundMatrix covA;
0487   covA.setZero();
0488   covA.diagonal() << 1, 2, 3, 4, 5, 6;
0489 
0490   Vector3 global = planeSurfaceA->localToGlobal(gctx, parA.head<2>());
0491 
0492   Transform3 transform;
0493   transform.setIdentity();
0494   transform.rotate(AngleAxis3(pRx, Vector3::UnitX()));
0495   transform.rotate(AngleAxis3(pRy, Vector3::UnitY()));
0496   transform.rotate(AngleAxis3(pRz, Vector3::UnitZ()));
0497   transform.translation() = global;
0498 
0499   auto perigee = Surface::makeShared<PerigeeSurface>(transform);
0500 
0501   auto [parB, covB] =
0502       boundToBound(parA, covA, *planeSurfaceA, *perigee, bField);
0503 
0504   // now go back
0505   auto [parA2, covA2] =
0506       boundToBound(parB, covB, *perigee, *planeSurfaceA, bField);
0507   CHECK_CLOSE_ABS(parA, parA2, 1e-9);
0508   CHECK_CLOSE_COVARIANCE(covA, covA2, 1e-9);
0509 
0510   auto prop = makePropagator(bField);
0511   BoundMatrix J =
0512       numericalBoundToBoundJacobian(prop, parA, *planeSurfaceA, *perigee);
0513   BoundMatrix covC = J * covA * J.transpose();
0514   CHECK_CLOSE_ABS(covB, covC, 1e-7);
0515 }
0516 
0517 }  // namespace Acts::Test