Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2025-08-06 08:11:25

0001 // This file is part of the Acts project.
0002 //
0003 // Copyright (C) 2020 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 http://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 <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 /// These tests do not test for a correct covariance transport but only for the
0051 /// correct conservation or modification of certain variables. A test suite for
0052 /// the numerical correctness is performed in the integration tests.
0053 BOOST_AUTO_TEST_CASE(covariance_engine_test) {
0054   // Create a test context
0055   GeometryContext tgContext = GeometryContext();
0056 
0057   auto particleHypothesis = ParticleHypothesis::pion();
0058 
0059   // Build a start vector
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   // Build covariance matrix, jacobians and related components
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   // Covariance transport to curvilinear coordinates
0078   detail::transportCovarianceToCurvilinear(covariance, jacobian,
0079                                            transportJacobian, derivatives,
0080                                            boundToFreeJacobian, direction);
0081 
0082   // Tests to see that the right components are (un-)changed
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   // Reset
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   // Repeat transport to surface
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   // Produce a curvilinear state without covariance matrix
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   // Reset
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   // Produce a curvilinear state with covariance matrix
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   // Produce a bound state without covariance matrix
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   // Reset
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   // Produce a bound state with covariance matrix
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   // Reset
0169   freeToBoundCorrection.apply = true;
0170 
0171   // Produce a bound state with free to bound correction
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   // identical surface, this should work
0291   auto [parB, covB] =
0292       boundToBound(parA, covA, *planeSurfaceA, *planeSurfaceB, bField);
0293 
0294   // these should be the same because the plane surface are the same
0295   CHECK_CLOSE_ABS(parA, parB, 1e-9);
0296   CHECK_CLOSE_COVARIANCE(covA, covB, 1e-9);
0297 
0298   // now go back
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   // sanity check that the normal didn't change
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   // loc0 and loc1 are rotated
0344   exp.head<2>() = Eigen::Rotation2D<double>(-angle) * parA.head<2>();
0345 
0346   CHECK_CLOSE_ABS(exp, parB, 1e-9);
0347 
0348   // now go back
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   // make plane that is slightly rotated
0373   Transform3 transform;
0374   transform = planeSurfaceA->transform(gctx).rotation();
0375 
0376   // figure out rotation axis along local x
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   // loc 0 must be zero so we're on the intersection of both surfaces.
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   // now go back
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   // make plane that is slightly rotated
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   // loc 1 must be zero so we're on the intersection of both surfaces.
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   // now go back
0444   auto [parA2, covA2] =
0445       boundToBound(parB, covB, *planeSurfaceB, *planeSurfaceA, bField);
0446   CHECK_CLOSE_ABS(parA, parA2, 1e-9);
0447   // tolerance is a bit higher here
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   // now go back
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 }  // namespace Acts::Test