Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-14 08:22:27

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