Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:12:50

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/tools/output_test_stream.hpp>
0011 #include <boost/test/unit_test.hpp>
0012 
0013 #include "Acts/Definitions/Algebra.hpp"
0014 #include "Acts/Definitions/Tolerance.hpp"
0015 #include "Acts/Definitions/Units.hpp"
0016 #include "Acts/Geometry/Extent.hpp"
0017 #include "Acts/Geometry/GeometryContext.hpp"
0018 #include "Acts/Geometry/Polyhedron.hpp"
0019 #include "Acts/Surfaces/CylinderBounds.hpp"
0020 #include "Acts/Surfaces/CylinderSurface.hpp"
0021 #include "Acts/Surfaces/Surface.hpp"
0022 #include "Acts/Surfaces/SurfaceBounds.hpp"
0023 #include "Acts/Surfaces/SurfaceMergingException.hpp"
0024 #include "Acts/Tests/CommonHelpers/DetectorElementStub.hpp"
0025 #include "Acts/Tests/CommonHelpers/FloatComparisons.hpp"
0026 #include "Acts/Utilities/BinningType.hpp"
0027 #include "Acts/Utilities/Intersection.hpp"
0028 #include "Acts/Utilities/Logger.hpp"
0029 #include "Acts/Utilities/Result.hpp"
0030 #include "Acts/Utilities/ThrowAssert.hpp"
0031 #include "Acts/Utilities/detail/periodic.hpp"
0032 
0033 #include <cmath>
0034 #include <memory>
0035 #include <numbers>
0036 #include <string>
0037 
0038 using namespace Acts::UnitLiterals;
0039 
0040 namespace Acts::Test {
0041 
0042 auto logger = Acts::getDefaultLogger("UnitTests", Acts::Logging::VERBOSE);
0043 
0044 // Create a test context
0045 GeometryContext testContext = GeometryContext();
0046 
0047 BOOST_AUTO_TEST_SUITE(CylinderSurfaces)
0048 /// Unit test for creating compliant/non-compliant CylinderSurface object
0049 BOOST_AUTO_TEST_CASE(CylinderSurfaceConstruction) {
0050   /// Test default construction
0051   // default construction is deleted
0052 
0053   /// Constructor with transform, radius and halfZ
0054   const double radius = 1.;
0055   const double halfZ = 10.;
0056   const double halfPhiSector = std::numbers::pi / 8.;
0057   const Translation3 translation{0., 1., 2.};
0058 
0059   auto pTransform = Transform3(translation);
0060   BOOST_CHECK_EQUAL(
0061       Surface::makeShared<CylinderSurface>(pTransform, radius, halfZ)->type(),
0062       Surface::Cylinder);
0063 
0064   /// Constructor with transform pointer, radius, halfZ and halfPhiSector
0065   BOOST_CHECK_EQUAL(Surface::makeShared<CylinderSurface>(pTransform, radius,
0066                                                          halfZ, halfPhiSector)
0067                         ->type(),
0068                     Surface::Cylinder);
0069 
0070   /// Constructor with transform and CylinderBounds pointer
0071   auto pCylinderBounds = std::make_shared<const CylinderBounds>(radius, halfZ);
0072   BOOST_CHECK_EQUAL(
0073       Surface::makeShared<CylinderSurface>(pTransform, pCylinderBounds)->type(),
0074       Surface::Cylinder);
0075 
0076   /// Copy constructor
0077   auto cylinderSurfaceObject =
0078       Surface::makeShared<CylinderSurface>(pTransform, radius, halfZ);
0079   auto copiedCylinderSurface =
0080       Surface::makeShared<CylinderSurface>(*cylinderSurfaceObject);
0081   BOOST_CHECK_EQUAL(copiedCylinderSurface->type(), Surface::Cylinder);
0082   BOOST_CHECK(*copiedCylinderSurface == *cylinderSurfaceObject);
0083 
0084   /// Copied and transformed
0085   auto copiedTransformedCylinderSurface = Surface::makeShared<CylinderSurface>(
0086       testContext, *cylinderSurfaceObject, pTransform);
0087   BOOST_CHECK_EQUAL(copiedTransformedCylinderSurface->type(),
0088                     Surface::Cylinder);
0089 
0090   /// Construct with nullptr bounds
0091   BOOST_CHECK_THROW(auto nullBounds = Surface::makeShared<CylinderSurface>(
0092                         Transform3::Identity(), nullptr),
0093                     AssertionFailureException);
0094 }
0095 
0096 /// Unit test for testing CylinderSurface properties
0097 BOOST_AUTO_TEST_CASE(CylinderSurfaceProperties) {
0098   /// Test clone method
0099   const double radius = 1.;
0100   const double halfZ = 10.;
0101   const Translation3 translation{0., 1., 2.};
0102 
0103   auto pTransform = Transform3(translation);
0104   auto cylinderSurfaceObject =
0105       Surface::makeShared<CylinderSurface>(pTransform, radius, halfZ);
0106 
0107   /// Test type (redundant)
0108   BOOST_CHECK_EQUAL(cylinderSurfaceObject->type(), Surface::Cylinder);
0109 
0110   /// Test referencePosition
0111   Vector3 referencePosition{0., 1., 2.};
0112   CHECK_CLOSE_ABS(cylinderSurfaceObject->referencePosition(
0113                       testContext, AxisDirection::AxisPhi),
0114                   referencePosition, 1e-9);
0115 
0116   /// Test referenceFrame
0117   const double invSqrt2 = 1. / std::numbers::sqrt2;
0118   Vector3 globalPosition{invSqrt2, 1. - invSqrt2, 0.};
0119   Vector3 globalPositionZ{invSqrt2, 1. - invSqrt2, 2.};
0120   Vector3 momentum{15., 15., 15.};
0121   Vector3 momentum2{6.6, -3., 2.};
0122   RotationMatrix3 expectedFrame;
0123   expectedFrame << invSqrt2, 0., invSqrt2, invSqrt2, 0., -invSqrt2, 0., 1., 0.;
0124   // check without shift
0125   CHECK_CLOSE_OR_SMALL(cylinderSurfaceObject->referenceFrame(
0126                            testContext, globalPosition, momentum),
0127                        expectedFrame, 1e-6, 1e-9);
0128   // check with shift and different momentum
0129   CHECK_CLOSE_OR_SMALL(cylinderSurfaceObject->referenceFrame(
0130                            testContext, globalPositionZ, momentum2),
0131                        expectedFrame, 1e-6, 1e-9);
0132 
0133   /// Test normal, given 3D position
0134   Vector3 origin{0., 0., 0.};
0135   Vector3 normal3D = {0., -1., 0.};
0136   CHECK_CLOSE_ABS(cylinderSurfaceObject->normal(testContext, origin), normal3D,
0137                   1e-9);
0138 
0139   Vector3 pos45deg = {invSqrt2, 1 + invSqrt2, 0.};
0140   Vector3 pos45degZ = {invSqrt2, 1 + invSqrt2, 4.};
0141   Vector3 normal45deg = {invSqrt2, invSqrt2, 0.};
0142   // test the normal vector
0143   CHECK_CLOSE_ABS(cylinderSurfaceObject->normal(testContext, pos45deg),
0144                   normal45deg, 1e-6 * invSqrt2);
0145   // test that the normal vector is independent of z coordinate
0146   CHECK_CLOSE_ABS(cylinderSurfaceObject->normal(testContext, pos45degZ),
0147                   normal45deg, 1e-6 * invSqrt2);
0148 
0149   /// Test normal given 2D rphi position
0150   Vector2 positionPiBy2(1., 0.);
0151   Vector3 normalAtPiBy2{std::cos(1.), std::sin(1.), 0.};
0152   CHECK_CLOSE_ABS(cylinderSurfaceObject->normal(testContext, positionPiBy2),
0153                   normalAtPiBy2, 1e-9);
0154 
0155   /// Test rotational symmetry axis
0156   Vector3 symmetryAxis{0., 0., 1.};
0157   CHECK_CLOSE_ABS(cylinderSurfaceObject->rotSymmetryAxis(testContext),
0158                   symmetryAxis, 1e-9);
0159 
0160   /// Test bounds
0161   BOOST_CHECK_EQUAL(cylinderSurfaceObject->bounds().type(),
0162                     SurfaceBounds::eCylinder);
0163 
0164   /// Test localToGlobal
0165   Vector2 localPosition{0., 0.};
0166   globalPosition = cylinderSurfaceObject->localToGlobal(
0167       testContext, localPosition, momentum);
0168   Vector3 expectedPosition{1, 1, 2};
0169   BOOST_CHECK_EQUAL(globalPosition, expectedPosition);
0170 
0171   /// Testing globalToLocal
0172   localPosition = cylinderSurfaceObject
0173                       ->globalToLocal(testContext, globalPosition, momentum)
0174                       .value();
0175   Vector2 expectedLocalPosition{0., 0.};
0176   BOOST_CHECK_EQUAL(localPosition, expectedLocalPosition);
0177 
0178   /// Test isOnSurface
0179   Vector3 offSurface{100, 1, 2};
0180   BOOST_CHECK(cylinderSurfaceObject->isOnSurface(
0181       testContext, globalPosition, momentum, BoundaryTolerance::None()));
0182   BOOST_CHECK(cylinderSurfaceObject->isOnSurface(testContext, globalPosition,
0183                                                  BoundaryTolerance::None()));
0184   BOOST_CHECK(!cylinderSurfaceObject->isOnSurface(
0185       testContext, offSurface, momentum, BoundaryTolerance::None()));
0186   BOOST_CHECK(!cylinderSurfaceObject->isOnSurface(testContext, offSurface,
0187                                                   BoundaryTolerance::None()));
0188 
0189   /// Intersection test
0190   Vector3 direction{-1., 0, 0};
0191   auto sfIntersection = cylinderSurfaceObject->intersect(
0192       testContext, offSurface, direction, BoundaryTolerance::Infinite());
0193   Intersection3D expectedIntersect{Vector3{1, 1, 2}, 99.,
0194                                    IntersectionStatus::reachable};
0195   BOOST_CHECK(sfIntersection[0].isValid());
0196   CHECK_CLOSE_ABS(sfIntersection[0].position(), expectedIntersect.position(),
0197                   1e-9);
0198   CHECK_CLOSE_ABS(sfIntersection[0].pathLength(),
0199                   expectedIntersect.pathLength(), 1e-9);
0200   // there is a second solution & and it should be valid
0201   BOOST_CHECK(sfIntersection[1].isValid());
0202   // And it's path should be further away then the primary solution
0203   double pn = sfIntersection[0].pathLength();
0204   double pa = sfIntersection[1].pathLength();
0205   BOOST_CHECK_LT(std::abs(pn), std::abs(pa));
0206   BOOST_CHECK_EQUAL(sfIntersection.object(), cylinderSurfaceObject.get());
0207 
0208   /// Test pathCorrection
0209   CHECK_CLOSE_REL(cylinderSurfaceObject->pathCorrection(testContext, offSurface,
0210                                                         momentum.normalized()),
0211                   std::numbers::sqrt3, 0.01);
0212 
0213   /// Test name
0214   BOOST_CHECK_EQUAL(cylinderSurfaceObject->name(),
0215                     std::string("Acts::CylinderSurface"));
0216 
0217   /// Test dump
0218   boost::test_tools::output_test_stream dumpOutput;
0219   std::string expected =
0220       "Acts::CylinderSurface\n\
0221      Center position  (x, y, z) = (0.0000, 1.0000, 2.0000)\n\
0222      Rotation:             colX = (1.000000, 0.000000, 0.000000)\n\
0223                            colY = (0.000000, 1.000000, 0.000000)\n\
0224                            colZ = (0.000000, 0.000000, 1.000000)\n\
0225      Bounds  : Acts::CylinderBounds: (radius, halfLengthZ, halfPhiSector, averagePhi, bevelMinZ, bevelMaxZ) = (1.0000000, 10.0000000, 3.1415927, 0.0000000, 0.0000000, 0.0000000)";
0226   dumpOutput << cylinderSurfaceObject->toStream(testContext);
0227   BOOST_CHECK(dumpOutput.is_equal(expected));
0228 }
0229 
0230 BOOST_AUTO_TEST_CASE(CylinderSurfaceEqualityOperators) {
0231   const double radius = 1.;
0232   const double halfZ = 10.;
0233   const Translation3 translation{0., 1., 2.};
0234 
0235   auto pTransform = Transform3(translation);
0236   auto cylinderSurfaceObject =
0237       Surface::makeShared<CylinderSurface>(pTransform, radius, halfZ);
0238 
0239   auto cylinderSurfaceObject2 =
0240       Surface::makeShared<CylinderSurface>(pTransform, radius, halfZ);
0241 
0242   /// Test equality operator
0243   BOOST_CHECK(*cylinderSurfaceObject == *cylinderSurfaceObject2);
0244 
0245   BOOST_TEST_CHECKPOINT(
0246       "Create and then assign a CylinderSurface object to the existing one");
0247   /// Test assignment
0248   auto assignedCylinderSurface =
0249       Surface::makeShared<CylinderSurface>(Transform3::Identity(), 6.6, 5.4);
0250   *assignedCylinderSurface = *cylinderSurfaceObject;
0251   /// Test equality of assigned to original
0252   BOOST_CHECK(*assignedCylinderSurface == *cylinderSurfaceObject);
0253 }
0254 
0255 /// Unit test for testing CylinderSurface properties
0256 BOOST_AUTO_TEST_CASE(CylinderSurfaceExtent) {
0257   using enum AxisDirection;
0258 
0259   // Some radius and half length
0260   const double radius = 1.;
0261   const double halfZ = 10.;
0262   const Translation3 translation{0., 0., 2.};  // != {0., 1., 2.}
0263 
0264   auto pTransform = Transform3(translation);
0265   auto cylinderSurface =
0266       Surface::makeShared<CylinderSurface>(pTransform, radius, halfZ);
0267   // The Extent, let's measure it
0268   auto cylinderExtent =
0269       cylinderSurface->polyhedronRepresentation(testContext, 1).extent();
0270 
0271   CHECK_CLOSE_ABS(-8, cylinderExtent.min(AxisZ), s_onSurfaceTolerance);
0272   CHECK_CLOSE_ABS(12, cylinderExtent.max(AxisZ), s_onSurfaceTolerance);
0273   CHECK_CLOSE_ABS(radius, cylinderExtent.min(AxisR), s_onSurfaceTolerance);
0274   CHECK_CLOSE_ABS(radius, cylinderExtent.max(AxisR), s_onSurfaceTolerance);
0275   CHECK_CLOSE_ABS(-radius, cylinderExtent.min(AxisX), s_onSurfaceTolerance);
0276   CHECK_CLOSE_ABS(radius, cylinderExtent.max(AxisX), s_onSurfaceTolerance);
0277   CHECK_CLOSE_ABS(-radius, cylinderExtent.min(AxisY), s_onSurfaceTolerance);
0278   CHECK_CLOSE_ABS(radius, cylinderExtent.max(AxisY), s_onSurfaceTolerance);
0279 }
0280 
0281 /// Unit test for testing CylinderSurface alignment derivatives
0282 BOOST_AUTO_TEST_CASE(CylinderSurfaceAlignment) {
0283   const double radius = 1.;
0284   const double halfZ = 10.;
0285   const Translation3 translation{0., 1., 2.};
0286 
0287   auto pTransform = Transform3(translation);
0288   auto cylinderSurfaceObject =
0289       Surface::makeShared<CylinderSurface>(pTransform, radius, halfZ);
0290 
0291   const auto& rotation = pTransform.rotation();
0292   // The local frame z axis
0293   const Vector3 localZAxis = rotation.col(2);
0294   // Check the local z axis is aligned to global z axis
0295   CHECK_CLOSE_ABS(localZAxis, Vector3(0., 0., 1.), 1e-15);
0296 
0297   /// Define the track (global) position and direction
0298   Vector3 globalPosition{0, 2, 2};
0299 
0300   // Test the derivative of bound track parameters local position w.r.t.
0301   // position in local 3D Cartesian coordinates
0302   const auto& loc3DToLocBound =
0303       cylinderSurfaceObject->localCartesianToBoundLocalDerivative(
0304           testContext, globalPosition);
0305   // Check if the result is as expected
0306   ActsMatrix<2, 3> expLoc3DToLocBound = ActsMatrix<2, 3>::Zero();
0307   expLoc3DToLocBound << -1, 0, 0, 0, 0, 1;
0308   CHECK_CLOSE_ABS(loc3DToLocBound, expLoc3DToLocBound, 1e-10);
0309 }
0310 
0311 BOOST_AUTO_TEST_CASE(CylinderSurfaceBinningPosition) {
0312   using namespace Acts::UnitLiterals;
0313   Vector3 s{5_mm, 7_mm, 10_cm};
0314   Transform3 trf;
0315   trf = Translation3(s) * AngleAxis3{0.5, Vector3::UnitZ()};
0316 
0317   double r = 300;
0318   double halfZ = 330;
0319   double averagePhi = 0.1;
0320 
0321   auto bounds = std::make_shared<CylinderBounds>(r, halfZ, std::numbers::pi / 8,
0322                                                  averagePhi);
0323   auto cylinder = Acts::Surface::makeShared<CylinderSurface>(trf, bounds);
0324 
0325   Vector3 exp = Vector3{r * std::cos(averagePhi), r * std::sin(averagePhi), 0};
0326   exp = trf * exp;
0327 
0328   Vector3 bp = cylinder->referencePosition(testContext, AxisDirection::AxisR);
0329   CHECK_CLOSE_ABS(bp, exp, 1e-10);
0330   CHECK_CLOSE_ABS(
0331       cylinder->referencePositionValue(testContext, AxisDirection::AxisR),
0332       VectorHelpers::perp(exp), 1e-10);
0333 
0334   bp = cylinder->referencePosition(testContext, AxisDirection::AxisRPhi);
0335   CHECK_CLOSE_ABS(bp, exp, 1e-10);
0336   CHECK_CLOSE_ABS(
0337       cylinder->referencePositionValue(testContext, AxisDirection::AxisRPhi),
0338       VectorHelpers::phi(exp) * VectorHelpers::perp(exp), 1e-10);
0339 
0340   for (auto b : {AxisDirection::AxisX, AxisDirection::AxisY,
0341                  AxisDirection::AxisZ, AxisDirection::AxisEta,
0342                  AxisDirection::AxisTheta, AxisDirection::AxisMag}) {
0343     BOOST_TEST_CONTEXT("binValue: " << b) {
0344       BOOST_CHECK_EQUAL(cylinder->referencePosition(testContext, b),
0345                         cylinder->center(testContext));
0346     }
0347   }
0348 }
0349 
0350 BOOST_AUTO_TEST_SUITE(CylinderSurfaceMerging)
0351 
0352 BOOST_AUTO_TEST_CASE(InvalidDetectorElement) {
0353   DetectorElementStub detElem;
0354 
0355   auto bounds = std::make_shared<CylinderBounds>(100_mm, 100_mm);
0356   auto cyl1 = Surface::makeShared<CylinderSurface>(bounds, detElem);
0357   auto cyl2 = Surface::makeShared<CylinderSurface>(bounds, detElem);
0358 
0359   BOOST_CHECK_THROW(
0360       cyl1->mergedWith(*cyl2, Acts::AxisDirection::AxisR, false, *logger),
0361       SurfaceMergingException);
0362 }
0363 
0364 BOOST_DATA_TEST_CASE(IncompatibleZDirection,
0365                      (boost::unit_test::data::xrange(-135, 180, 45) *
0366                       boost::unit_test::data::make(Vector3{0_mm, 0_mm, 0_mm},
0367                                                    Vector3{20_mm, 0_mm, 0_mm},
0368                                                    Vector3{0_mm, 20_mm, 0_mm},
0369                                                    Vector3{20_mm, 20_mm, 0_mm},
0370                                                    Vector3{0_mm, 0_mm, 20_mm})),
0371                      angle, offset) {
0372   Logging::ScopedFailureThreshold ft{Logging::FATAL};
0373 
0374   Transform3 base =
0375       AngleAxis3(angle * 1_degree, Vector3::UnitX()) * Translation3(offset);
0376 
0377   auto cyl = Surface::makeShared<CylinderSurface>(base, 30_mm, 100_mm);
0378   auto cyl2 = Surface::makeShared<CylinderSurface>(
0379       base * Translation3{Vector3::UnitZ() * 200_mm}, 30_mm, 100_mm);
0380 
0381   BOOST_CHECK_THROW(
0382       cyl->mergedWith(*cyl2, Acts::AxisDirection::AxisPhi, false, *logger),
0383       SurfaceMergingException);
0384 
0385   auto cylShiftedXy = Surface::makeShared<CylinderSurface>(
0386       base * Translation3{Vector3{1_mm, 2_mm, 200_mm}}, 30_mm, 100_mm);
0387   BOOST_CHECK_THROW(cyl->mergedWith(*cylShiftedXy, Acts::AxisDirection::AxisZ,
0388                                     false, *logger),
0389                     SurfaceMergingException);
0390 
0391   auto cylRotatedX = Surface::makeShared<CylinderSurface>(
0392       base * AngleAxis3{10_degree, Vector3::UnitX()} *
0393           Translation3{Vector3::UnitZ() * 200_mm},
0394       30_mm, 100_mm);
0395   BOOST_CHECK_THROW(
0396       cyl->mergedWith(*cylRotatedX, Acts::AxisDirection::AxisZ, false, *logger),
0397       SurfaceMergingException);
0398 
0399   // Cylinder with different radius
0400   auto cyl3 = Surface::makeShared<CylinderSurface>(
0401       base * Translation3{Vector3::UnitZ() * 200_mm}, 35_mm, 100_mm);
0402   BOOST_CHECK_THROW(
0403       cyl->mergedWith(*cyl3, Acts::AxisDirection::AxisZ, false, *logger),
0404       SurfaceMergingException);
0405 
0406   // Cylinder with bevel
0407   auto cyl4 = Surface::makeShared<CylinderSurface>(
0408       base * Translation3{Vector3::UnitZ() * 200_mm}, 30_mm, 100_mm,
0409       std::numbers::pi, 0, std::numbers::pi / 8.);
0410   BOOST_CHECK_THROW(
0411       cyl->mergedWith(*cyl4, Acts::AxisDirection::AxisZ, false, *logger),
0412       SurfaceMergingException);
0413 
0414   auto cyl5 = Surface::makeShared<CylinderSurface>(
0415       base * Translation3{Vector3::UnitZ() * 200_mm}, 30_mm, 100_mm,
0416       std::numbers::pi, 0, 0, std::numbers::pi / 8.);
0417   BOOST_CHECK_THROW(
0418       cyl->mergedWith(*cyl5, Acts::AxisDirection::AxisZ, false, *logger),
0419       SurfaceMergingException);
0420 
0421   // Cylinder with overlap in z
0422   auto cyl6 = Surface::makeShared<CylinderSurface>(
0423       base * Translation3{Vector3::UnitZ() * 150_mm}, 30_mm, 100_mm);
0424   BOOST_CHECK_THROW(
0425       cyl->mergedWith(*cyl6, Acts::AxisDirection::AxisZ, false, *logger),
0426       SurfaceMergingException);
0427 
0428   // Cylinder with gap in z
0429   auto cyl7 = Surface::makeShared<CylinderSurface>(
0430       base * Translation3{Vector3::UnitZ() * 250_mm}, 30_mm, 100_mm);
0431   BOOST_CHECK_THROW(
0432       cyl->mergedWith(*cyl7, Acts::AxisDirection::AxisZ, false, *logger),
0433       SurfaceMergingException);
0434 
0435   // Cylinder with phi sector and relative z rotation
0436   auto cyl8 = Surface::makeShared<CylinderSurface>(
0437       base * AngleAxis3(14_degree, Vector3::UnitZ()) *
0438           Translation3{Vector3::UnitZ() * 200_mm},
0439       30_mm, 100_mm, 10_degree, 40_degree);
0440   BOOST_CHECK_THROW(
0441       cyl->mergedWith(*cyl8, Acts::AxisDirection::AxisZ, false, *logger),
0442       SurfaceMergingException);
0443 
0444   auto cylPhi1 = Surface::makeShared<CylinderSurface>(Transform3::Identity(),
0445                                                       30_mm, 100_mm, 45_degree);
0446   auto cylPhi2 = Surface::makeShared<CylinderSurface>(
0447       Transform3{Translation3{Vector3::UnitZ() * 150_mm}}, 30_mm, 50_mm,
0448       55_degree);
0449   BOOST_CHECK_THROW(
0450       cylPhi1->mergedWith(*cylPhi2, Acts::AxisDirection::AxisZ, false, *logger),
0451       SurfaceMergingException);
0452 }
0453 
0454 BOOST_DATA_TEST_CASE(ZDirection,
0455                      (boost::unit_test::data::xrange(-135, 180, 45) *
0456                       boost::unit_test::data::make(Vector3{0_mm, 0_mm, 0_mm},
0457                                                    Vector3{20_mm, 0_mm, 0_mm},
0458                                                    Vector3{0_mm, 20_mm, 0_mm},
0459                                                    Vector3{20_mm, 20_mm, 0_mm},
0460                                                    Vector3{0_mm, 0_mm, 20_mm})),
0461                      angle, offset) {
0462   Transform3 base =
0463       AngleAxis3(angle * 1_degree, Vector3::UnitX()) * Translation3(offset);
0464 
0465   auto cyl = Surface::makeShared<CylinderSurface>(base, 30_mm, 100_mm);
0466 
0467   auto cyl2 = Surface::makeShared<CylinderSurface>(
0468       base * AngleAxis3(14_degree, Vector3::UnitZ()) *
0469           Translation3{Vector3::UnitZ() * 200_mm},
0470       30_mm, 100_mm);
0471 
0472   auto [cyl3, reversed] =
0473       cyl->mergedWith(*cyl2, Acts::AxisDirection::AxisZ, false, *logger);
0474   BOOST_REQUIRE_NE(cyl3, nullptr);
0475   BOOST_CHECK(!reversed);
0476 
0477   auto [cyl3Reversed, reversed2] =
0478       cyl2->mergedWith(*cyl, Acts::AxisDirection::AxisZ, false, *logger);
0479   BOOST_REQUIRE_NE(cyl3Reversed, nullptr);
0480   BOOST_CHECK(cyl3->bounds() == cyl3Reversed->bounds());
0481   BOOST_CHECK(reversed2);
0482 
0483   auto bounds = cyl3->bounds();
0484 
0485   BOOST_CHECK_EQUAL(bounds.get(CylinderBounds::eR), 30_mm);
0486   BOOST_CHECK_EQUAL(bounds.get(CylinderBounds::eHalfLengthZ), 200_mm);
0487   BOOST_CHECK_EQUAL(bounds.get(CylinderBounds::eAveragePhi), 0_degree);
0488   BOOST_CHECK_EQUAL(bounds.get(CylinderBounds::eHalfPhiSector), 180_degree);
0489 
0490   // Rotation in z depends on the ordering, the left side "wins"
0491   Transform3 expected12 = base * Translation3{Vector3::UnitZ() * 100_mm};
0492   BOOST_CHECK_EQUAL(expected12.matrix(), cyl3->transform(testContext).matrix());
0493 
0494   Transform3 expected21 = base * AngleAxis3(14_degree, Vector3::UnitZ()) *
0495                           Translation3{Vector3::UnitZ() * 100_mm};
0496   CHECK_CLOSE_OR_SMALL(cyl3Reversed->transform(testContext).matrix(),
0497                        expected21.matrix(), 1e-6, 1e-10);
0498 
0499   auto cylPhi1 = Surface::makeShared<CylinderSurface>(Transform3::Identity(),
0500                                                       30_mm, 100_mm, 45_degree);
0501   auto cylPhi2 = Surface::makeShared<CylinderSurface>(
0502       Transform3{Translation3{Vector3::UnitZ() * 150_mm}}, 30_mm, 50_mm,
0503       45_degree);
0504 
0505   auto [cylPhi12, reversedPhy12] =
0506       cylPhi1->mergedWith(*cylPhi2, Acts::AxisDirection::AxisZ, false, *logger);
0507 
0508   BOOST_REQUIRE_NE(cylPhi12, nullptr);
0509   auto boundsPhi12 = cylPhi12->bounds();
0510   BOOST_CHECK_EQUAL(boundsPhi12.get(CylinderBounds::eR), 30_mm);
0511   BOOST_CHECK_EQUAL(boundsPhi12.get(CylinderBounds::eHalfLengthZ), 150_mm);
0512   BOOST_CHECK_EQUAL(boundsPhi12.get(CylinderBounds::eAveragePhi), 0_degree);
0513   BOOST_CHECK_EQUAL(boundsPhi12.get(CylinderBounds::eHalfPhiSector), 45_degree);
0514 }
0515 
0516 BOOST_DATA_TEST_CASE(IncompatibleRPhiDirection,
0517                      (boost::unit_test::data::xrange(-135, 180, 45) *
0518                       boost::unit_test::data::make(Vector3{0_mm, 0_mm, 0_mm},
0519                                                    Vector3{20_mm, 0_mm, 0_mm},
0520                                                    Vector3{0_mm, 20_mm, 0_mm},
0521                                                    Vector3{20_mm, 20_mm, 0_mm},
0522                                                    Vector3{0_mm, 0_mm, 20_mm}) *
0523                       boost::unit_test::data::xrange(-1300, 1300, 104)),
0524                      angle, offset, phiShift) {
0525   Logging::ScopedFailureThreshold ft{Logging::FATAL};
0526   Transform3 base =
0527       AngleAxis3(angle * 1_degree, Vector3::UnitX()) * Translation3(offset);
0528 
0529   auto a = [phiShift](double v) {
0530     return detail::radian_sym(v + phiShift * 1_degree);
0531   };
0532 
0533   auto cylPhi = Surface::makeShared<CylinderSurface>(base, 30_mm, 100_mm,
0534                                                      10_degree, a(40_degree));
0535 
0536   // Cylinder with overlap in phi
0537   auto cylPhi2 = Surface::makeShared<CylinderSurface>(base, 30_mm, 100_mm,
0538                                                       45_degree, a(85_degree));
0539   BOOST_CHECK_THROW(cylPhi->mergedWith(*cylPhi2, Acts::AxisDirection::AxisRPhi,
0540                                        false, *logger),
0541                     SurfaceMergingException);
0542 
0543   // Cylinder with gap in phi
0544   auto cylPhi3 = Surface::makeShared<CylinderSurface>(base, 30_mm, 100_mm,
0545                                                       45_degree, a(105_degree));
0546   BOOST_CHECK_THROW(cylPhi->mergedWith(*cylPhi3, Acts::AxisDirection::AxisRPhi,
0547                                        false, *logger),
0548                     SurfaceMergingException);
0549 
0550   // Cylinder with a z shift
0551   auto cylPhi4 = Surface::makeShared<CylinderSurface>(
0552       base * Translation3{Vector3::UnitZ() * 20_mm}, 30_mm, 100_mm, 45_degree,
0553       a(95_degree));
0554   BOOST_CHECK_THROW(cylPhi->mergedWith(*cylPhi4, Acts::AxisDirection::AxisRPhi,
0555                                        false, *logger),
0556                     SurfaceMergingException);
0557 
0558   // Test phi sector with different z halflengths
0559   auto cylPhi5 = Surface::makeShared<CylinderSurface>(base, 30_mm, 110_mm,
0560                                                       45_degree, a(95_degree));
0561   BOOST_CHECK_THROW(cylPhi->mergedWith(*cylPhi5, Acts::AxisDirection::AxisRPhi,
0562                                        false, *logger),
0563                     SurfaceMergingException);
0564 }
0565 
0566 BOOST_DATA_TEST_CASE(RPhiDirection,
0567                      (boost::unit_test::data::xrange(-135, 180, 45) *
0568                       boost::unit_test::data::make(Vector3{0_mm, 0_mm, 0_mm},
0569                                                    Vector3{20_mm, 0_mm, 0_mm},
0570                                                    Vector3{0_mm, 20_mm, 0_mm},
0571                                                    Vector3{20_mm, 20_mm, 0_mm},
0572                                                    Vector3{0_mm, 0_mm, 20_mm}) *
0573                       boost::unit_test::data::xrange(-1300, 1300, 104)),
0574                      angle, offset, phiShift) {
0575   Transform3 base =
0576       AngleAxis3(angle * 1_degree, Vector3::UnitX()) * Translation3(offset);
0577 
0578   auto a = [phiShift](double v) {
0579     return detail::radian_sym(v + phiShift * 1_degree);
0580   };
0581 
0582   BOOST_TEST_CONTEXT("Internal rotation") {
0583     auto cyl = Surface::makeShared<CylinderSurface>(base, 30_mm, 100_mm,
0584                                                     10_degree, a(40_degree));
0585     auto cyl2 = Surface::makeShared<CylinderSurface>(base, 30_mm, 100_mm,
0586                                                      45_degree, a(95_degree));
0587 
0588     auto [cyl3, reversed] =
0589         cyl->mergedWith(*cyl2, Acts::AxisDirection::AxisRPhi, false, *logger);
0590     BOOST_REQUIRE_NE(cyl3, nullptr);
0591     BOOST_CHECK_EQUAL(base.matrix(), cyl3->transform(testContext).matrix());
0592     BOOST_CHECK(reversed);
0593 
0594     auto [cyl3Reversed, reversed2] =
0595         cyl2->mergedWith(*cyl, Acts::AxisDirection::AxisRPhi, false, *logger);
0596     BOOST_REQUIRE_NE(cyl3Reversed, nullptr);
0597     BOOST_CHECK(*cyl3 == *cyl3Reversed);
0598     BOOST_CHECK(!reversed2);
0599 
0600     const auto& bounds = cyl3->bounds();
0601 
0602     BOOST_CHECK_SMALL(
0603         detail::difference_periodic(bounds.get(CylinderBounds::eAveragePhi),
0604                                     a(85_degree), 2 * std::numbers::pi),
0605         1e-6);
0606     BOOST_CHECK_CLOSE(bounds.get(CylinderBounds::eHalfPhiSector), 55_degree,
0607                       0.1);
0608 
0609     auto cyl4 = Surface::makeShared<CylinderSurface>(base, 30_mm, 100_mm,
0610                                                      20_degree, a(170_degree));
0611     auto cyl5 = Surface::makeShared<CylinderSurface>(base, 30_mm, 100_mm,
0612                                                      10_degree, a(-160_degree));
0613     auto [cyl45, reversed45] =
0614         cyl4->mergedWith(*cyl5, Acts::AxisDirection::AxisRPhi, false, *logger);
0615     BOOST_REQUIRE_NE(cyl45, nullptr);
0616     BOOST_CHECK_EQUAL(base.matrix(), cyl45->transform(testContext).matrix());
0617     BOOST_CHECK(reversed45);
0618 
0619     auto [cyl54, reversed54] =
0620         cyl5->mergedWith(*cyl4, Acts::AxisDirection::AxisRPhi, false, *logger);
0621     BOOST_REQUIRE_NE(cyl54, nullptr);
0622     BOOST_CHECK(!reversed54);
0623 
0624     BOOST_CHECK(*cyl54 == *cyl45);
0625 
0626     BOOST_CHECK_SMALL(detail::difference_periodic(
0627                           cyl45->bounds().get(CylinderBounds::eAveragePhi),
0628                           a(180_degree), 2 * std::numbers::pi),
0629                       1e-6);
0630     BOOST_CHECK_CLOSE(cyl45->bounds().get(CylinderBounds::eHalfPhiSector),
0631                       30_degree, 1e-6);
0632 
0633     auto cyl6 = Surface::makeShared<CylinderSurface>(base, 30_mm, 100_mm,
0634                                                      90_degree, a(90_degree));
0635     auto cyl7 = Surface::makeShared<CylinderSurface>(base, 30_mm, 100_mm,
0636                                                      90_degree, a(-90_degree));
0637 
0638     auto [cyl67, reversed67] =
0639         cyl6->mergedWith(*cyl7, Acts::AxisDirection::AxisRPhi, false, *logger);
0640     BOOST_REQUIRE_NE(cyl67, nullptr);
0641     BOOST_CHECK_EQUAL(base.matrix(), cyl67->transform(testContext).matrix());
0642 
0643     auto [cyl76, reversed76] =
0644         cyl7->mergedWith(*cyl6, Acts::AxisDirection::AxisRPhi, false, *logger);
0645     BOOST_REQUIRE_NE(cyl76, nullptr);
0646     BOOST_CHECK_EQUAL(base.matrix(), cyl76->transform(testContext).matrix());
0647 
0648     // The ordering in this case is effectively arbitrary, you get the ordering
0649     // you put in
0650     BOOST_CHECK(!reversed67);
0651     BOOST_CHECK(!reversed76);
0652 
0653     BOOST_CHECK_SMALL(detail::difference_periodic(
0654                           cyl67->bounds().get(CylinderBounds::eAveragePhi),
0655                           a(180_degree), 2 * std::numbers::pi),
0656                       1e-6);
0657     BOOST_CHECK_CLOSE(cyl67->bounds().get(CylinderBounds::eHalfPhiSector),
0658                       180_degree, 1e-6);
0659   }
0660 
0661   BOOST_TEST_CONTEXT("External rotation") {
0662     Transform3 trf1 = base * AngleAxis3(a(40_degree), Vector3::UnitZ());
0663     auto cyl1 = Surface::makeShared<CylinderSurface>(trf1, 30_mm, 100_mm,
0664                                                      10_degree, 0_degree);
0665 
0666     Transform3 trf2 = base * AngleAxis3(a(95_degree), Vector3::UnitZ());
0667     auto cyl2 = Surface::makeShared<CylinderSurface>(trf2, 30_mm, 100_mm,
0668                                                      45_degree, 0_degree);
0669 
0670     auto [cyl3, reversed] =
0671         cyl1->mergedWith(*cyl2, Acts::AxisDirection::AxisRPhi, true, *logger);
0672 
0673     BOOST_REQUIRE_NE(cyl3, nullptr);
0674     Transform3 trfExpected12 =
0675         base * AngleAxis3(a(85_degree), Vector3::UnitZ());
0676     CHECK_CLOSE_OR_SMALL(cyl3->transform(testContext).matrix(),
0677                          trfExpected12.matrix(), 1e-6, 1e-10);
0678     BOOST_CHECK(reversed);
0679 
0680     BOOST_CHECK_EQUAL(cyl3->bounds().get(CylinderBounds::eAveragePhi), 0);
0681     BOOST_CHECK_CLOSE(cyl3->bounds().get(CylinderBounds::eHalfPhiSector),
0682                       55_degree, 1e-6);
0683 
0684     Transform3 trf4 = base * AngleAxis3(a(170_degree), Vector3::UnitZ());
0685     auto cyl4 = Surface::makeShared<CylinderSurface>(trf4, 30_mm, 100_mm,
0686                                                      20_degree, 0_degree);
0687     Transform3 trf5 = base * AngleAxis3(a(-160_degree), Vector3::UnitZ());
0688     auto cyl5 = Surface::makeShared<CylinderSurface>(trf5, 30_mm, 100_mm,
0689                                                      10_degree, 0_degree);
0690     auto [cyl45, reversed45] =
0691         cyl4->mergedWith(*cyl5, Acts::AxisDirection::AxisRPhi, true, *logger);
0692     BOOST_REQUIRE_NE(cyl45, nullptr);
0693     Transform3 trfExpected45 =
0694         base * AngleAxis3(a(180_degree), Vector3::UnitZ());
0695     CHECK_CLOSE_OR_SMALL(cyl45->transform(testContext).matrix(),
0696                          trfExpected45.matrix(), 1e-6, 1e-10);
0697     BOOST_CHECK(reversed45);
0698 
0699     auto [cyl54, reversed54] =
0700         cyl5->mergedWith(*cyl4, Acts::AxisDirection::AxisRPhi, true, *logger);
0701     BOOST_REQUIRE_NE(cyl54, nullptr);
0702     BOOST_CHECK(!reversed54);
0703 
0704     BOOST_CHECK(*cyl54 == *cyl45);
0705 
0706     BOOST_CHECK_EQUAL(cyl45->bounds().get(CylinderBounds::eAveragePhi), 0);
0707     BOOST_CHECK_CLOSE(cyl45->bounds().get(CylinderBounds::eHalfPhiSector),
0708                       30_degree, 1e-6);
0709 
0710     Transform3 trf6 = base * AngleAxis3(a(90_degree), Vector3::UnitZ());
0711     auto cyl6 = Surface::makeShared<CylinderSurface>(trf6, 30_mm, 100_mm,
0712                                                      90_degree, 0_degree);
0713     Transform3 trf7 = base * AngleAxis3(a(-90_degree), Vector3::UnitZ());
0714     auto cyl7 = Surface::makeShared<CylinderSurface>(trf7, 30_mm, 100_mm,
0715                                                      90_degree, 0_degree);
0716 
0717     auto [cyl67, reversed67] =
0718         cyl6->mergedWith(*cyl7, Acts::AxisDirection::AxisRPhi, true, *logger);
0719     BOOST_REQUIRE_NE(cyl67, nullptr);
0720     Transform3 expected67 = trf6 * AngleAxis3(90_degree, Vector3::UnitZ());
0721     CHECK_CLOSE_OR_SMALL(cyl67->transform(testContext).matrix(),
0722                          expected67.matrix(), 1e-6, 1e-10);
0723 
0724     auto [cyl76, reversed76] =
0725         cyl7->mergedWith(*cyl6, Acts::AxisDirection::AxisRPhi, true, *logger);
0726     BOOST_REQUIRE_NE(cyl76, nullptr);
0727     Transform3 expected76 = trf7 * AngleAxis3(90_degree, Vector3::UnitZ());
0728     CHECK_CLOSE_OR_SMALL(cyl76->transform(testContext).matrix(),
0729                          expected76.matrix(), 1e-6, 1e-10);
0730 
0731     // The ordering in this case is effectively arbitrary, you get the ordering
0732     // you put in
0733     BOOST_CHECK(!reversed67);
0734     BOOST_CHECK(!reversed76);
0735 
0736     BOOST_CHECK_EQUAL(cyl67->bounds().get(CylinderBounds::eAveragePhi), 0);
0737     BOOST_CHECK_CLOSE(cyl67->bounds().get(CylinderBounds::eHalfPhiSector),
0738                       180_degree, 0.1);
0739   }
0740 }
0741 
0742 BOOST_AUTO_TEST_SUITE_END()
0743 BOOST_AUTO_TEST_SUITE_END()
0744 
0745 }  // namespace Acts::Test