Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-09-18 08:14:17

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