Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-10-05 08:14:14

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/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 /// 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::dangerouslyDefaultConstruct();
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{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   // 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(direction,
0091                     Vector3(std::sqrt(5. / 22.), 3. * std::sqrt(2. / 55.),
0092                             7. / std::sqrt(110.)));
0093 
0094   // Reset
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   // Repeat transport to surface
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   // Produce curvilinear parameters without covariance matrix
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   // Produce curvilinear parameters with covariance matrix
0125   curvPars =
0126       detail::curvilinearParameters(parameters, covariance, particleHypothesis);
0127   BOOST_CHECK(curvPars.covariance().has_value());
0128   BOOST_CHECK_EQUAL(*curvPars.covariance(), covariance);
0129 
0130   // Produce bound parameters without covariance matrix
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   // Produce bound parameters with covariance matrix
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   // Reset
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   // Transport to the surface with free to bound correction
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   // The time is transported along the path to the destination surface, so it
0192   // is part of the comparison against the analytical jacobian.
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   // identical surface, this should work
0270   auto [parB, covB] =
0271       boundToBound(parA, covA, *planeSurfaceA, *planeSurfaceB, bField);
0272 
0273   // these should be the same because the plane surface are the same
0274   CHECK_CLOSE_ABS(parA, parB, 1e-9);
0275   CHECK_CLOSE_COVARIANCE(covA, covB, 1e-9);
0276 
0277   // now go back
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   // sanity check that the normal didn't change
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   // loc0 and loc1 are rotated
0325   exp.head<2>() = Eigen::Rotation2D<double>(-angle) * parA.head<2>();
0326 
0327   CHECK_CLOSE_ABS(exp, parB, 1e-9);
0328 
0329   // now go back
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   // make plane that is slightly rotated
0354   Transform3 transform;
0355   transform = planeSurfaceA->localToGlobalTransform(gctx).rotation();
0356 
0357   // figure out rotation axis along local x
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   // loc 0 must be zero so we're on the intersection of both surfaces.
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   // now go back
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   // make plane that is slightly rotated
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   // loc 1 must be zero so we're on the intersection of both surfaces.
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   // now go back
0431   auto [parA2, covA2] =
0432       boundToBound(parB, covB, *planeSurfaceB, *planeSurfaceA, bField);
0433   CHECK_CLOSE_ABS(parA, parA2, 1e-9);
0434   // tolerance is a bit higher here
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   // now go back
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 }  // namespace Acts::Test