Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-07-20 07:53:20

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/tools/old/interface.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/Alignment.hpp"
0015 #include "Acts/Definitions/Tolerance.hpp"
0016 #include "Acts/Definitions/TrackParametrization.hpp"
0017 #include "Acts/Definitions/Units.hpp"
0018 #include "Acts/Geometry/Extent.hpp"
0019 #include "Acts/Geometry/GeometryContext.hpp"
0020 #include "Acts/Geometry/Polyhedron.hpp"
0021 #include "Acts/Material/BinnedSurfaceMaterial.hpp"
0022 #include "Acts/Material/HomogeneousSurfaceMaterial.hpp"
0023 #include "Acts/Material/Material.hpp"
0024 #include "Acts/Material/MaterialSlab.hpp"
0025 #include "Acts/Surfaces/PlaneSurface.hpp"
0026 #include "Acts/Surfaces/RectangleBounds.hpp"
0027 #include "Acts/Surfaces/Surface.hpp"
0028 #include "Acts/Surfaces/SurfaceBounds.hpp"
0029 #include "Acts/Surfaces/SurfaceMergingException.hpp"
0030 #include "Acts/Surfaces/TrapezoidBounds.hpp"
0031 #include "Acts/Utilities/AxisDefinitions.hpp"
0032 #include "Acts/Utilities/BinUtility.hpp"
0033 #include "Acts/Utilities/BinningType.hpp"
0034 #include "Acts/Utilities/Intersection.hpp"
0035 #include "Acts/Utilities/Result.hpp"
0036 #include "Acts/Utilities/ThrowAssert.hpp"
0037 #include "ActsTests/CommonHelpers/DetectorElementStub.hpp"
0038 #include "ActsTests/CommonHelpers/FloatComparisons.hpp"
0039 
0040 #include <cmath>
0041 #include <memory>
0042 #include <numbers>
0043 #include <string>
0044 
0045 using namespace Acts;
0046 using namespace Acts::UnitLiterals;
0047 
0048 namespace ActsTests {
0049 
0050 // Create a test context
0051 GeometryContext tgContext = GeometryContext::dangerouslyDefaultConstruct();
0052 
0053 BOOST_AUTO_TEST_SUITE(SurfacesSuite)
0054 
0055 /// Unit test for creating compliant/non-compliant PlaneSurface object
0056 BOOST_AUTO_TEST_CASE(PlaneSurfaceConstruction) {
0057   /// Test default construction
0058   // default construction is deleted
0059 
0060   // bounds object, rectangle type
0061   auto rBounds = std::make_shared<const RectangleBounds>(3., 4.);
0062   /// Constructor with transform and bounds
0063   Translation3 translation{0., 1., 2.};
0064   auto pTransform = Transform3(translation);
0065 
0066   /// Constructor with transform
0067   BOOST_CHECK_EQUAL(
0068       Surface::makeShared<PlaneSurface>(pTransform, rBounds)->type(),
0069       Surface::Plane);
0070 
0071   /// Copy constructor
0072   auto planeSurfaceObject =
0073       Surface::makeShared<PlaneSurface>(pTransform, rBounds);
0074   auto copiedPlaneSurface =
0075       Surface::makeShared<PlaneSurface>(*planeSurfaceObject);
0076   BOOST_CHECK_EQUAL(copiedPlaneSurface->type(), Surface::Plane);
0077   BOOST_CHECK(*copiedPlaneSurface == *planeSurfaceObject);
0078 
0079   /// Copied and transformed
0080   auto copiedTransformedPlaneSurface = Surface::makeShared<PlaneSurface>(
0081       tgContext, *planeSurfaceObject, pTransform);
0082   BOOST_CHECK_EQUAL(copiedTransformedPlaneSurface->type(), Surface::Plane);
0083 
0084   /// Construct with nullptr bounds
0085   DetectorElementStub detElem;
0086   BOOST_CHECK_THROW(
0087       auto nullBounds = Surface::makeShared<PlaneSurface>(nullptr, detElem),
0088       AssertionFailureException);
0089 }
0090 
0091 /// Unit test for testing PlaneSurface properties
0092 BOOST_AUTO_TEST_CASE(PlaneSurfaceProperties) {
0093   // bounds object, rectangle type
0094   auto rBounds = std::make_shared<const RectangleBounds>(3., 4.);
0095 
0096   /// Test clone method
0097   Translation3 translation{0., 1., 2.};
0098   auto pTransform = Transform3(translation);
0099   auto planeSurfaceObject =
0100       Surface::makeShared<PlaneSurface>(pTransform, rBounds);
0101   // Is it in the right place?
0102   Translation3 translation2{0., 2., 4.};
0103   auto pTransform2 = Transform3(translation2);
0104   auto planeSurfaceObject2 =
0105       Surface::makeShared<PlaneSurface>(pTransform2, rBounds);
0106 
0107   /// Test type (redundant)
0108   BOOST_CHECK_EQUAL(planeSurfaceObject->type(), Surface::Plane);
0109 
0110   /// Test referencePosition
0111   Vector3 referencePosition{0., 1., 2.};
0112   BOOST_CHECK_EQUAL(
0113       planeSurfaceObject->referencePosition(tgContext, AxisDirection::AxisX),
0114       referencePosition);
0115 
0116   /// Test referenceFrame
0117   Vector3 arbitraryGlobalPosition{2., 2., 2.};
0118   Vector3 momentum{1.e6, 1.e6, 1.e6};
0119   RotationMatrix3 expectedFrame;
0120   expectedFrame << 1., 0., 0., 0., 1., 0., 0., 0., 1.;
0121 
0122   CHECK_CLOSE_OR_SMALL(planeSurfaceObject->referenceFrame(
0123                            tgContext, arbitraryGlobalPosition, momentum),
0124                        expectedFrame, 1e-6, 1e-9);
0125 
0126   /// Test normal, given 3D position
0127   Vector3 normal3D(0., 0., 1.);
0128   BOOST_CHECK_EQUAL(planeSurfaceObject->normal(tgContext), normal3D);
0129 
0130   /// Test bounds
0131   BOOST_CHECK_EQUAL(planeSurfaceObject->bounds().type(),
0132                     SurfaceBounds::eRectangle);
0133 
0134   /// Test localToGlobal
0135   Vector2 localPosition{1.5, 1.7};
0136   Vector3 globalPosition =
0137       planeSurfaceObject->localToGlobal(tgContext, localPosition, momentum);
0138   // expected position is the translated one
0139   Vector3 expectedPosition{1.5 + translation.x(), 1.7 + translation.y(),
0140                            translation.z()};
0141 
0142   CHECK_CLOSE_REL(globalPosition, expectedPosition, 1e-2);
0143 
0144   /// Testing globalToLocal
0145   localPosition =
0146       planeSurfaceObject->globalToLocal(tgContext, globalPosition, momentum)
0147           .value();
0148   Vector2 expectedLocalPosition{1.5, 1.7};
0149 
0150   CHECK_CLOSE_REL(localPosition, expectedLocalPosition, 1e-2);
0151 
0152   Vector3 globalPositionOff =
0153       globalPosition +
0154       planeSurfaceObject->normal(tgContext, localPosition) * 0.1;
0155 
0156   BOOST_CHECK(
0157       planeSurfaceObject->globalToLocal(tgContext, globalPositionOff, momentum)
0158           .error());
0159   BOOST_CHECK(planeSurfaceObject
0160                   ->globalToLocal(tgContext, globalPositionOff, momentum, 0.05)
0161                   .error());
0162   BOOST_CHECK(planeSurfaceObject
0163                   ->globalToLocal(tgContext, globalPositionOff, momentum, 0.2)
0164                   .ok());
0165 
0166   /// Test isOnSurface
0167   Vector3 offSurface{0, 1, -2.};
0168   BOOST_CHECK(planeSurfaceObject->isOnSurface(
0169       tgContext, globalPosition, momentum, BoundaryTolerance::None()));
0170   BOOST_CHECK(planeSurfaceObject->isOnSurface(tgContext, globalPosition,
0171                                               BoundaryTolerance::None()));
0172   BOOST_CHECK(!planeSurfaceObject->isOnSurface(tgContext, offSurface, momentum,
0173                                                BoundaryTolerance::None()));
0174   BOOST_CHECK(!planeSurfaceObject->isOnSurface(tgContext, offSurface,
0175                                                BoundaryTolerance::None()));
0176 
0177   /// Test intersection
0178   Vector3 direction{0., 0., 1.};
0179   Intersection3D sfIntersection =
0180       planeSurfaceObject
0181           ->intersect(tgContext, offSurface, direction,
0182                       BoundaryTolerance::None())
0183           .closest();
0184   Intersection3D expectedIntersect{Vector3{0, 1, 2}, 4.,
0185                                    IntersectionStatus::reachable};
0186   BOOST_CHECK(sfIntersection.isValid());
0187   BOOST_CHECK_EQUAL(sfIntersection.position(), expectedIntersect.position());
0188   BOOST_CHECK_EQUAL(sfIntersection.pathLength(),
0189                     expectedIntersect.pathLength());
0190 
0191   /// Test pathCorrection
0192   CHECK_CLOSE_REL(planeSurfaceObject->pathCorrection(tgContext, offSurface,
0193                                                      momentum.normalized()),
0194                   std::numbers::sqrt3, 0.01);
0195 
0196   /// Test name
0197   BOOST_CHECK_EQUAL(planeSurfaceObject->name(),
0198                     std::string("Acts::PlaneSurface"));
0199 
0200   /// Test dump
0201   boost::test_tools::output_test_stream dumpOutput;
0202   dumpOutput << planeSurfaceObject->toStream(tgContext);
0203   BOOST_CHECK(dumpOutput.is_equal(
0204       "Acts::PlaneSurface\n"
0205       "     Center position  (x, y, z) = (0.0000, 1.0000, 2.0000)\n"
0206       "     Rotation:             colX = (1.000000, 0.000000, 0.000000)\n"
0207       "                           colY = (0.000000, 1.000000, 0.000000)\n"
0208       "                           colZ = (0.000000, 0.000000, 1.000000)\n"
0209       "     Bounds  : Acts::RectangleBounds:  (hlX, hlY) = (3.0000000, "
0210       "4.0000000)\n"
0211       "(lower left, upper right):\n"
0212       "-3.0000000 -4.0000000\n"
0213       "3.0000000 4.0000000"));
0214 }
0215 
0216 BOOST_AUTO_TEST_CASE(PlaneSurfaceEqualityOperators) {
0217   // rectangle bounds
0218   auto rBounds = std::make_shared<const RectangleBounds>(3., 4.);
0219   Translation3 translation{0., 1., 2.};
0220   auto pTransform = Transform3(translation);
0221   auto planeSurfaceObject =
0222       Surface::makeShared<PlaneSurface>(pTransform, rBounds);
0223   auto planeSurfaceObject2 =
0224       Surface::makeShared<PlaneSurface>(pTransform, rBounds);
0225 
0226   /// Test equality operator
0227   BOOST_CHECK(*planeSurfaceObject == *planeSurfaceObject2);
0228 
0229   BOOST_TEST_CHECKPOINT(
0230       "Create and then assign a PlaneSurface object to the existing one");
0231 
0232   /// Test assignment
0233   auto assignedPlaneSurface =
0234       Surface::makeShared<PlaneSurface>(Transform3::Identity(), nullptr);
0235   *assignedPlaneSurface = *planeSurfaceObject;
0236 
0237   /// Test equality of assigned to original
0238   BOOST_CHECK(*assignedPlaneSurface == *planeSurfaceObject);
0239 }
0240 
0241 /// Unit test for testing PlaneSurface extent via Polyhedron representation
0242 BOOST_AUTO_TEST_CASE(PlaneSurfaceExtent) {
0243   // First test - non-rotated
0244   static const Transform3 planeZX =
0245       AngleAxis3(-std::numbers::pi / 2., Vector3::UnitX()) *
0246       AngleAxis3(-std::numbers::pi / 2., Vector3::UnitZ()) *
0247       Transform3::Identity();
0248 
0249   double rHx = 2.;
0250   double rHy = 4.;
0251   double yPs = 3.;
0252   auto rBounds = std::make_shared<RectangleBounds>(rHx, rHy);
0253 
0254   auto plane = Surface::makeShared<PlaneSurface>(
0255       Transform3(Translation3(Vector3(0., yPs, 0.)) * planeZX), rBounds);
0256 
0257   auto planeExtent = plane->polyhedronRepresentation(tgContext, 1).extent();
0258 
0259   CHECK_CLOSE_ABS(planeExtent.min(AxisDirection::AxisZ), -rHx,
0260                   s_onSurfaceTolerance);
0261   CHECK_CLOSE_ABS(planeExtent.max(AxisDirection::AxisZ), rHx,
0262                   s_onSurfaceTolerance);
0263   CHECK_CLOSE_ABS(planeExtent.min(AxisDirection::AxisX), -rHy,
0264                   s_onSurfaceTolerance);
0265   CHECK_CLOSE_ABS(planeExtent.max(AxisDirection::AxisX), rHy,
0266                   s_onSurfaceTolerance);
0267   CHECK_CLOSE_ABS(planeExtent.min(AxisDirection::AxisY), yPs,
0268                   s_onSurfaceTolerance);
0269   CHECK_CLOSE_ABS(planeExtent.max(AxisDirection::AxisY), yPs,
0270                   s_onSurfaceTolerance);
0271   CHECK_CLOSE_ABS(planeExtent.min(AxisDirection::AxisR), yPs,
0272                   s_onSurfaceTolerance);
0273   CHECK_CLOSE_ABS(planeExtent.max(AxisDirection::AxisR), std::hypot(yPs, rHy),
0274                   s_onSurfaceTolerance);
0275 
0276   // Now rotate
0277   double alpha = 0.123;
0278   auto planeRot = Surface::makeShared<PlaneSurface>(
0279       Transform3(Translation3(Vector3(0., yPs, 0.)) *
0280                  AngleAxis3(alpha, Vector3(0., 0., 1.)) * planeZX),
0281       rBounds);
0282 
0283   auto planeExtentRot =
0284       planeRot->polyhedronRepresentation(tgContext, 1).extent();
0285   CHECK_CLOSE_ABS(planeExtentRot.min(AxisDirection::AxisZ), -rHx,
0286                   s_onSurfaceTolerance);
0287   CHECK_CLOSE_ABS(planeExtentRot.max(AxisDirection::AxisZ), rHx,
0288                   s_onSurfaceTolerance);
0289   CHECK_CLOSE_ABS(planeExtentRot.min(AxisDirection::AxisX),
0290                   -rHy * std::cos(alpha), s_onSurfaceTolerance);
0291   CHECK_CLOSE_ABS(planeExtentRot.max(AxisDirection::AxisX),
0292                   rHy * std::cos(alpha), s_onSurfaceTolerance);
0293   CHECK_CLOSE_ABS(planeExtentRot.min(AxisDirection::AxisY),
0294                   yPs - rHy * std::sin(alpha), s_onSurfaceTolerance);
0295   CHECK_CLOSE_ABS(planeExtentRot.max(AxisDirection::AxisY),
0296                   yPs + rHy * std::sin(alpha), s_onSurfaceTolerance);
0297   CHECK_CLOSE_ABS(planeExtentRot.min(AxisDirection::AxisR),
0298                   yPs * std::cos(alpha), s_onSurfaceTolerance);
0299 }
0300 
0301 BOOST_AUTO_TEST_CASE(RotatedTrapezoid) {
0302   const double shortHalfX = 100.;
0303   const double longHalfX = 200.;
0304   const double halfY = 300.;
0305   const double rotAngle = 45._degree;
0306 
0307   Vector2 edgePoint{longHalfX - 10., halfY};
0308 
0309   std::shared_ptr<TrapezoidBounds> bounds =
0310       std::make_shared<TrapezoidBounds>(shortHalfX, longHalfX, halfY);
0311 
0312   BOOST_CHECK(bounds->inside(edgePoint, BoundaryTolerance::None()));
0313   BOOST_CHECK(!bounds->inside(Eigen::Rotation2D(-rotAngle) * edgePoint,
0314                               BoundaryTolerance::None()));
0315 
0316   std::shared_ptr<TrapezoidBounds> rotatedBounds =
0317       std::make_shared<TrapezoidBounds>(shortHalfX, longHalfX, halfY, rotAngle);
0318 
0319   BOOST_CHECK(!rotatedBounds->inside(edgePoint, BoundaryTolerance::None()));
0320   BOOST_CHECK(rotatedBounds->inside(Eigen::Rotation2D(-rotAngle) * edgePoint,
0321                                     BoundaryTolerance::None()));
0322 }
0323 
0324 /// Unit test for testing PlaneSurface alignment derivatives
0325 BOOST_AUTO_TEST_CASE(PlaneSurfaceAlignment) {
0326   // bounds object, rectangle type
0327   auto rBounds = std::make_shared<const RectangleBounds>(3., 4.);
0328   // Test clone method
0329   Translation3 translation{0., 1., 2.};
0330   const double rotationAngle = std::numbers::pi / 2.;
0331   AngleAxis3 rotation(rotationAngle, Vector3::UnitY());
0332   RotationMatrix3 rotationMat = rotation.toRotationMatrix();
0333 
0334   auto pTransform = Transform3{translation * rotationMat};
0335   auto planeSurfaceObject =
0336       Surface::makeShared<PlaneSurface>(pTransform, rBounds);
0337 
0338   // The local frame z axis
0339   const Vector3 localZAxis = rotationMat.col(2);
0340   // Check the local z axis is aligned to global x axis
0341   CHECK_CLOSE_ABS(localZAxis, Vector3(1., 0., 0.), 1e-15);
0342 
0343   // Define the track (local) position and direction
0344   Vector2 localPosition{1, 2};
0345   Vector3 momentum{1, 0, 0};
0346   Vector3 direction = momentum.normalized();
0347   // Get the global position
0348   Vector3 globalPosition =
0349       planeSurfaceObject->localToGlobal(tgContext, localPosition, momentum);
0350 
0351   // (a) Test the derivative of path length w.r.t. alignment parameters
0352   const AlignmentToPathMatrix& alignToPath =
0353       planeSurfaceObject->alignmentToPathDerivative(tgContext, globalPosition,
0354                                                     direction);
0355   // The expected results
0356   AlignmentToPathMatrix expAlignToPath = AlignmentToPathMatrix::Zero();
0357   expAlignToPath << 1, 0, 0, 2, -1, 0;
0358 
0359   // Check if the calculated derivative is as expected
0360   CHECK_CLOSE_ABS(alignToPath, expAlignToPath, 1e-10);
0361 
0362   // (b) Test the derivative of bound track parameters local position w.r.t.
0363   // position in local 3D Cartesian coordinates
0364   const auto& loc3DToLocBound =
0365       planeSurfaceObject->localCartesianToBoundLocalDerivative(tgContext,
0366                                                                globalPosition);
0367   // For plane surface, this should be identity matrix
0368   CHECK_CLOSE_ABS(loc3DToLocBound, (Matrix<2, 3>::Identity()), 1e-10);
0369 
0370   // (c) Test the derivative of bound parameters (only test loc0, loc1 here)
0371   // w.r.t. alignment parameters
0372   FreeVector derivatives = FreeVector::Zero();
0373   derivatives.head<3>() = direction;
0374   const AlignmentToBoundMatrix& alignToBound =
0375       planeSurfaceObject->alignmentToBoundDerivative(tgContext, globalPosition,
0376                                                      direction, derivatives);
0377   const AlignmentToPathMatrix alignToloc0 =
0378       alignToBound.block<1, 6>(eBoundLoc0, eAlignmentCenter0);
0379   const AlignmentToPathMatrix alignToloc1 =
0380       alignToBound.block<1, 6>(eBoundLoc1, eAlignmentCenter0);
0381   // The expected results
0382   AlignmentToPathMatrix expAlignToloc0;
0383   expAlignToloc0 << 0, 0, 1, 0, 0, 2;
0384   AlignmentToPathMatrix expAlignToloc1;
0385   expAlignToloc1 << 0, -1, 0, 0, 0, -1;
0386   // Check if the calculated derivatives are as expected
0387   CHECK_CLOSE_ABS(alignToloc0, expAlignToloc0, 1e-10);
0388   CHECK_CLOSE_ABS(alignToloc1, expAlignToloc1, 1e-10);
0389 }
0390 
0391 BOOST_AUTO_TEST_SUITE(PlaneSurfaceMerging)
0392 
0393 auto logger = Acts::getDefaultLogger("UnitTests", Acts::Logging::VERBOSE);
0394 
0395 // Create a test context
0396 GeometryContext gctx = GeometryContext::dangerouslyDefaultConstruct();
0397 
0398 auto rBounds = std::make_shared<const RectangleBounds>(1., 2.);
0399 
0400 BOOST_AUTO_TEST_CASE(SurfaceOverlap) {
0401   // Correct orientation, overlapping along merging direction
0402   Translation3 offsetX{4., 0., 0.};
0403   Translation3 offsetY{0., 2., 0.};
0404 
0405   Transform3 base(Translation3::Identity());
0406   Transform3 otherX = base * offsetX;
0407   Transform3 otherY = base * offsetY;
0408 
0409   auto plane = Surface::makeShared<PlaneSurface>(base, rBounds);
0410   auto planeX = Surface::makeShared<PlaneSurface>(otherX, rBounds);
0411   auto planeY = Surface::makeShared<PlaneSurface>(otherY, rBounds);
0412 
0413   BOOST_CHECK_THROW(plane->mergedWith(*planeX, Acts::AxisDirection::AxisX),
0414                     SurfaceMergingException);
0415   BOOST_CHECK_THROW(plane->mergedWith(*planeY, Acts::AxisDirection::AxisY),
0416                     SurfaceMergingException);
0417 
0418   BOOST_CHECK_THROW(planeX->mergedWith(*plane, Acts::AxisDirection::AxisX),
0419                     SurfaceMergingException);
0420   BOOST_CHECK_THROW(planeY->mergedWith(*plane, Acts::AxisDirection::AxisY),
0421                     SurfaceMergingException);
0422 }
0423 
0424 BOOST_AUTO_TEST_CASE(SurfaceMisalignmentShift) {
0425   // Correct orientation, not aligned along orthogonal to merging direction
0426   Translation3 offsetX{2., 1., 0.};
0427   Translation3 offsetY{-1., 4., 0.};
0428   Translation3 offsetZ{0., 4., 1.};
0429 
0430   Transform3 base(Translation3::Identity());
0431   Transform3 otherX = base * offsetX;
0432   Transform3 otherY = base * offsetY;
0433   Transform3 otherZ = base * offsetZ;
0434 
0435   auto plane = Surface::makeShared<PlaneSurface>(base, rBounds);
0436   auto planeX = Surface::makeShared<PlaneSurface>(otherX, rBounds);
0437   auto planeY = Surface::makeShared<PlaneSurface>(otherY, rBounds);
0438   auto planeZ = Surface::makeShared<PlaneSurface>(otherZ, rBounds);
0439 
0440   BOOST_CHECK_THROW(plane->mergedWith(*planeX, Acts::AxisDirection::AxisX),
0441                     SurfaceMergingException);
0442   BOOST_CHECK_THROW(plane->mergedWith(*planeY, Acts::AxisDirection::AxisY),
0443                     SurfaceMergingException);
0444   BOOST_CHECK_THROW(plane->mergedWith(*planeZ, Acts::AxisDirection::AxisX),
0445                     SurfaceMergingException);
0446 
0447   BOOST_CHECK_THROW(planeX->mergedWith(*plane, Acts::AxisDirection::AxisX),
0448                     SurfaceMergingException);
0449   BOOST_CHECK_THROW(planeY->mergedWith(*plane, Acts::AxisDirection::AxisY),
0450                     SurfaceMergingException);
0451   BOOST_CHECK_THROW(planeZ->mergedWith(*plane, Acts::AxisDirection::AxisX),
0452                     SurfaceMergingException);
0453 }
0454 
0455 BOOST_AUTO_TEST_CASE(SurfaceMisalignedAngle) {
0456   // Correct positioning, rotated in different directions
0457   Translation3 offsetX{2., 0., 0.};
0458   Translation3 offsetY{0., 4., 0.};
0459 
0460   double angle = std::numbers::pi / 12;
0461   Transform3 base(Translation3::Identity());
0462   Transform3 otherX = base * offsetX * AngleAxis3(angle, Vector3::UnitZ());
0463   Transform3 otherY = base * offsetY * AngleAxis3(angle, Vector3::UnitY());
0464   Transform3 otherZ = base * offsetY * AngleAxis3(angle, Vector3::UnitZ());
0465 
0466   auto plane = Surface::makeShared<PlaneSurface>(base, rBounds);
0467   auto planeX = Surface::makeShared<PlaneSurface>(otherX, rBounds);
0468   auto planeY = Surface::makeShared<PlaneSurface>(otherY, rBounds);
0469   auto planeZ = Surface::makeShared<PlaneSurface>(otherZ, rBounds);
0470 
0471   BOOST_CHECK_THROW(plane->mergedWith(*planeX, Acts::AxisDirection::AxisX),
0472                     SurfaceMergingException);
0473   BOOST_CHECK_THROW(plane->mergedWith(*planeY, Acts::AxisDirection::AxisY),
0474                     SurfaceMergingException);
0475   BOOST_CHECK_THROW(plane->mergedWith(*planeZ, Acts::AxisDirection::AxisY),
0476                     SurfaceMergingException);
0477 
0478   BOOST_CHECK_THROW(planeX->mergedWith(*plane, Acts::AxisDirection::AxisX),
0479                     SurfaceMergingException);
0480   BOOST_CHECK_THROW(planeY->mergedWith(*plane, Acts::AxisDirection::AxisY),
0481                     SurfaceMergingException);
0482   BOOST_CHECK_THROW(planeZ->mergedWith(*plane, Acts::AxisDirection::AxisY),
0483                     SurfaceMergingException);
0484 }
0485 
0486 BOOST_AUTO_TEST_CASE(SurfaceDifferentBounds) {
0487   // Correct orientation and alignment, different bounds lengths along
0488   // orthogonal to merging direction
0489   Translation3 offset{2., 0., 0.};
0490 
0491   Transform3 base(Translation3::Identity());
0492   Transform3 other = base * offset;
0493 
0494   auto plane = Surface::makeShared<PlaneSurface>(base, rBounds);
0495 
0496   auto rBoundsOther = std::make_shared<const RectangleBounds>(2., 4.);
0497   auto planeOther = Surface::makeShared<PlaneSurface>(other, rBoundsOther);
0498 
0499   BOOST_CHECK_THROW(plane->mergedWith(*planeOther, Acts::AxisDirection::AxisX),
0500                     SurfaceMergingException);
0501 }
0502 
0503 BOOST_AUTO_TEST_CASE(XYDirection) {
0504   double angle = std::numbers::pi / 12;
0505   Translation3 offsetX{2., 0., 0.};
0506   Translation3 offsetY{0., 4., 0.};
0507 
0508   Transform3 base =
0509       AngleAxis3(angle, Vector3::UnitX()) * Translation3::Identity();
0510   Transform3 otherX = base * offsetX;
0511   Transform3 otherY = base * offsetY;
0512 
0513   auto plane = Surface::makeShared<PlaneSurface>(base, rBounds);
0514   auto planeX = Surface::makeShared<PlaneSurface>(otherX, rBounds);
0515   auto planeY = Surface::makeShared<PlaneSurface>(otherY, rBounds);
0516 
0517   BOOST_CHECK_THROW(plane->mergedWith(*planeX, Acts::AxisDirection::AxisZ),
0518                     SurfaceMergingException);
0519 
0520   auto expectedBoundsX = std::make_shared<const RectangleBounds>(2, 2);
0521   auto [planeXMerged, reversedX] =
0522       plane->mergedWith(*planeX, Acts::AxisDirection::AxisX, *logger);
0523   BOOST_REQUIRE_NE(planeXMerged, nullptr);
0524   BOOST_CHECK(!reversedX);
0525   BOOST_CHECK_EQUAL(planeXMerged->bounds(), *expectedBoundsX);
0526   BOOST_CHECK_EQUAL(planeXMerged->center(gctx), base * Vector3::UnitX() * 1);
0527 
0528   auto expectedBoundsY = std::make_shared<const RectangleBounds>(1, 4);
0529   auto [planeYMerged, reversedY] =
0530       plane->mergedWith(*planeY, Acts::AxisDirection::AxisY, *logger);
0531   BOOST_REQUIRE_NE(planeYMerged, nullptr);
0532   BOOST_CHECK(!reversedY);
0533   BOOST_CHECK_EQUAL(planeYMerged->bounds(), *expectedBoundsY);
0534   BOOST_CHECK_EQUAL(planeYMerged->center(gctx), base * Vector3::UnitY() * 2);
0535 
0536   auto [planeXMerged2, reversedX2] =
0537       planeX->mergedWith(*plane, Acts::AxisDirection::AxisX, *logger);
0538   BOOST_REQUIRE_NE(planeXMerged2, nullptr);
0539   BOOST_CHECK(planeXMerged->bounds() == planeXMerged2->bounds());
0540   BOOST_CHECK(reversedX2);
0541   BOOST_CHECK_EQUAL(planeXMerged2->bounds(), *expectedBoundsX);
0542   BOOST_CHECK_EQUAL(planeXMerged2->center(gctx), base * Vector3::UnitX() * 1);
0543 
0544   auto [planeYMerged2, reversedY2] =
0545       planeY->mergedWith(*plane, Acts::AxisDirection::AxisY, *logger);
0546   BOOST_REQUIRE_NE(planeYMerged2, nullptr);
0547   BOOST_CHECK(planeYMerged->bounds() == planeYMerged2->bounds());
0548   BOOST_CHECK(reversedY2);
0549   BOOST_CHECK_EQUAL(planeYMerged2->bounds(), *expectedBoundsY);
0550   BOOST_CHECK_EQUAL(planeYMerged2->center(gctx), base * Vector3::UnitY() * 2);
0551 }
0552 
0553 BOOST_AUTO_TEST_SUITE_END()
0554 
0555 BOOST_AUTO_TEST_CASE(PlaneSurfaceMaterialAssignment) {
0556   auto rBounds = std::make_shared<RectangleBounds>(100., 100.);
0557   auto surface =
0558       Surface::makeShared<PlaneSurface>(Transform3::Identity(), rBounds);
0559   MaterialSlab slab(Material::fromMolarDensity(1., 2., 3., 4., 5.), 0.1);
0560 
0561   // HomogeneousSurfaceMaterial is always valid
0562   auto homMat = std::make_shared<HomogeneousSurfaceMaterial>(slab);
0563   BOOST_CHECK_NO_THROW(surface->assignSurfaceMaterial(homMat));
0564   BOOST_CHECK_NE(surface->surfaceMaterial(), nullptr);
0565 
0566   // nullptr clears the material
0567   BOOST_CHECK_NO_THROW(surface->assignSurfaceMaterial(nullptr));
0568   BOOST_CHECK_EQUAL(surface->surfaceMaterial(), nullptr);
0569 
0570   // 1D AxisX - valid for plane
0571   BinUtility buX(10, -100.f, 100.f, Acts::open, AxisDirection::AxisX);
0572   auto matX = std::make_shared<BinnedSurfaceMaterial>(
0573       buX, MaterialSlabVector(10, slab));
0574   BOOST_CHECK_NO_THROW(surface->assignSurfaceMaterial(matX));
0575 
0576   // 1D AxisY - valid for plane
0577   BinUtility buY(10, -100.f, 100.f, Acts::open, AxisDirection::AxisY);
0578   auto matY = std::make_shared<BinnedSurfaceMaterial>(
0579       buY, MaterialSlabVector(10, slab));
0580   BOOST_CHECK_NO_THROW(surface->assignSurfaceMaterial(matY));
0581 
0582   // 2D {AxisX, AxisY} - valid for plane
0583   BinUtility bu2D(5, -100.f, 100.f, Acts::open, AxisDirection::AxisX);
0584   bu2D += BinUtility(3, -100.f, 100.f, Acts::open, AxisDirection::AxisY);
0585   auto mat2D = std::make_shared<BinnedSurfaceMaterial>(
0586       bu2D, MaterialSlabMatrix(3, MaterialSlabVector(5, slab)));
0587   BOOST_CHECK_NO_THROW(surface->assignSurfaceMaterial(mat2D));
0588 
0589   // Wrong axis directions - should throw
0590   for (auto badDir : {AxisDirection::AxisRPhi, AxisDirection::AxisZ,
0591                       AxisDirection::AxisR, AxisDirection::AxisPhi}) {
0592     BinUtility buBad(10, 0.f, 10.f, Acts::open, badDir);
0593     auto matBad = std::make_shared<BinnedSurfaceMaterial>(
0594         buBad, MaterialSlabVector(10, slab));
0595     BOOST_CHECK_THROW(surface->assignSurfaceMaterial(matBad),
0596                       std::invalid_argument);
0597   }
0598 }
0599 
0600 BOOST_AUTO_TEST_SUITE_END()
0601 
0602 }  // namespace ActsTests