Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:11:29

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 "Acts/Surfaces/CylinderSurface.hpp"
0010 
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Definitions/Tolerance.hpp"
0013 #include "Acts/Definitions/Units.hpp"
0014 #include "Acts/Geometry/GeometryObject.hpp"
0015 #include "Acts/Surfaces/CylinderBounds.hpp"
0016 #include "Acts/Surfaces/SurfaceError.hpp"
0017 #include "Acts/Surfaces/SurfaceMergingException.hpp"
0018 #include "Acts/Surfaces/detail/AlignmentHelper.hpp"
0019 #include "Acts/Surfaces/detail/FacesHelper.hpp"
0020 #include "Acts/Surfaces/detail/MergeHelper.hpp"
0021 #include "Acts/Utilities/BinningType.hpp"
0022 #include "Acts/Utilities/Intersection.hpp"
0023 #include "Acts/Utilities/ThrowAssert.hpp"
0024 #include "Acts/Utilities/detail/periodic.hpp"
0025 
0026 #include <algorithm>
0027 #include <cassert>
0028 #include <cmath>
0029 #include <iostream>
0030 #include <memory>
0031 #include <stdexcept>
0032 #include <utility>
0033 #include <vector>
0034 
0035 namespace Acts {
0036 
0037 using VectorHelpers::perp;
0038 using VectorHelpers::phi;
0039 
0040 CylinderSurface::CylinderSurface(const CylinderSurface& other)
0041     : GeometryObject(), RegularSurface(other), m_bounds(other.m_bounds) {}
0042 
0043 CylinderSurface::CylinderSurface(const GeometryContext& gctx,
0044                                  const CylinderSurface& other,
0045                                  const Transform3& shift)
0046     : GeometryObject(),
0047       RegularSurface(gctx, other, shift),
0048       m_bounds(other.m_bounds) {}
0049 
0050 CylinderSurface::CylinderSurface(const Transform3& transform, double radius,
0051                                  double halfz, double halfphi, double avphi,
0052                                  double bevelMinZ, double bevelMaxZ)
0053     : RegularSurface(transform),
0054       m_bounds(std::make_shared<const CylinderBounds>(
0055           radius, halfz, halfphi, avphi, bevelMinZ, bevelMaxZ)) {}
0056 
0057 CylinderSurface::CylinderSurface(std::shared_ptr<const CylinderBounds> cbounds,
0058                                  const DetectorElementBase& detelement)
0059     : RegularSurface(detelement), m_bounds(std::move(cbounds)) {
0060   // surfaces representing a detector element must have bounds
0061   throw_assert(m_bounds, "CylinderBounds must not be nullptr");
0062 }
0063 
0064 CylinderSurface::CylinderSurface(const Transform3& transform,
0065                                  std::shared_ptr<const CylinderBounds> cbounds)
0066     : RegularSurface(transform), m_bounds(std::move(cbounds)) {
0067   throw_assert(m_bounds, "CylinderBounds must not be nullptr");
0068 }
0069 
0070 CylinderSurface& CylinderSurface::operator=(const CylinderSurface& other) {
0071   if (this != &other) {
0072     Surface::operator=(other);
0073     m_bounds = other.m_bounds;
0074   }
0075   return *this;
0076 }
0077 
0078 // return the binning position for ordering in the BinnedArray
0079 Vector3 CylinderSurface::referencePosition(const GeometryContext& gctx,
0080                                            AxisDirection aDir) const {
0081   // special binning type for R-type methods
0082   if (aDir == AxisDirection::AxisR || aDir == AxisDirection::AxisRPhi) {
0083     double R = bounds().get(CylinderBounds::eR);
0084     double phi = bounds().get(CylinderBounds::eAveragePhi);
0085     return localToGlobal(gctx, Vector2{phi * R, 0}, Vector3{});
0086   }
0087   // give the center as default for all of these binning types
0088   // AxisDirection::AxisX, AxisDirection::AxisY, AxisDirection::AxisZ,
0089   // AxisDirection::AxisR, AxisDirection::AxisPhi, AxisDirection::AxisRPhi,
0090   // AxisDirection::AxisTheta, AxisDirection::AxisEta
0091   return center(gctx);
0092 }
0093 
0094 // return the measurement frame: it's the tangential plane
0095 RotationMatrix3 CylinderSurface::referenceFrame(
0096     const GeometryContext& gctx, const Vector3& position,
0097     const Vector3& /*direction*/) const {
0098   RotationMatrix3 mFrame;
0099   // construct the measurement frame
0100   // measured Y is the z axis
0101   Vector3 measY = rotSymmetryAxis(gctx);
0102   // measured z is the position normalized transverse (in local)
0103   Vector3 measDepth = normal(gctx, position);
0104   // measured X is what comoes out of it
0105   Vector3 measX(measY.cross(measDepth).normalized());
0106   // assign the columnes
0107   mFrame.col(0) = measX;
0108   mFrame.col(1) = measY;
0109   mFrame.col(2) = measDepth;
0110   // return the rotation matrix
0111   return mFrame;
0112 }
0113 
0114 Surface::SurfaceType CylinderSurface::type() const {
0115   return Surface::Cylinder;
0116 }
0117 
0118 Vector3 CylinderSurface::localToGlobal(const GeometryContext& gctx,
0119                                        const Vector2& lposition) const {
0120   // create the position in the local 3d frame
0121   double r = bounds().get(CylinderBounds::eR);
0122   double phi = lposition[0] / r;
0123   Vector3 position(r * cos(phi), r * sin(phi), lposition[1]);
0124   return transform(gctx) * position;
0125 }
0126 
0127 Result<Vector2> CylinderSurface::globalToLocal(const GeometryContext& gctx,
0128                                                const Vector3& position,
0129                                                double tolerance) const {
0130   double inttol = tolerance;
0131   if (tolerance == s_onSurfaceTolerance) {
0132     // transform default value!
0133     // @TODO: check if s_onSurfaceTolerance would do here
0134     inttol = bounds().get(CylinderBounds::eR) * 0.0001;
0135   }
0136   if (inttol < 0.01) {
0137     inttol = 0.01;
0138   }
0139   const Transform3& sfTransform = transform(gctx);
0140   Transform3 inverseTrans(sfTransform.inverse());
0141   Vector3 loc3Dframe(inverseTrans * position);
0142   if (std::abs(perp(loc3Dframe) - bounds().get(CylinderBounds::eR)) > inttol) {
0143     return Result<Vector2>::failure(SurfaceError::GlobalPositionNotOnSurface);
0144   }
0145   return Result<Vector2>::success(
0146       {bounds().get(CylinderBounds::eR) * phi(loc3Dframe), loc3Dframe.z()});
0147 }
0148 
0149 std::string CylinderSurface::name() const {
0150   return "Acts::CylinderSurface";
0151 }
0152 
0153 Vector3 CylinderSurface::normal(const GeometryContext& gctx,
0154                                 const Vector2& lposition) const {
0155   double phi = lposition[0] / m_bounds->get(CylinderBounds::eR);
0156   Vector3 localNormal(cos(phi), sin(phi), 0.);
0157   return transform(gctx).linear() * localNormal;
0158 }
0159 
0160 Vector3 CylinderSurface::normal(const GeometryContext& gctx,
0161                                 const Vector3& position) const {
0162   const Transform3& sfTransform = transform(gctx);
0163   // get it into the cylinder frame
0164   Vector3 pos3D = sfTransform.inverse() * position;
0165   // set the z coordinate to 0
0166   pos3D.z() = 0.;
0167   // normalize and rotate back into global
0168   return sfTransform.linear() * pos3D.normalized();
0169 }
0170 
0171 double CylinderSurface::pathCorrection(const GeometryContext& gctx,
0172                                        const Vector3& position,
0173                                        const Vector3& direction) const {
0174   Vector3 normalT = normal(gctx, position);
0175   double cosAlpha = normalT.dot(direction);
0176   return std::abs(1. / cosAlpha);
0177 }
0178 
0179 const CylinderBounds& CylinderSurface::bounds() const {
0180   return (*m_bounds.get());
0181 }
0182 
0183 Polyhedron CylinderSurface::polyhedronRepresentation(
0184     const GeometryContext& gctx, unsigned int quarterSegments) const {
0185   auto ctrans = transform(gctx);
0186 
0187   // Prepare vertices and faces
0188   std::vector<Vector3> vertices =
0189       bounds().circleVertices(ctrans, quarterSegments);
0190   auto [faces, triangularMesh] =
0191       detail::FacesHelper::cylindricalFaceMesh(vertices);
0192   return Polyhedron(vertices, faces, triangularMesh, false);
0193 }
0194 
0195 Vector3 CylinderSurface::rotSymmetryAxis(const GeometryContext& gctx) const {
0196   // fast access via transform matrix (and not rotation())
0197   return transform(gctx).matrix().block<3, 1>(0, 2);
0198 }
0199 
0200 detail::RealQuadraticEquation CylinderSurface::intersectionSolver(
0201     const Transform3& transform, const Vector3& position,
0202     const Vector3& direction) const {
0203   // Solve for radius R
0204   double R = bounds().get(CylinderBounds::eR);
0205 
0206   // Get the transformation matrtix
0207   const auto& tMatrix = transform.matrix();
0208   Vector3 caxis = tMatrix.block<3, 1>(0, 2).transpose();
0209   Vector3 ccenter = tMatrix.block<3, 1>(0, 3).transpose();
0210 
0211   // Check documentation for explanation
0212   Vector3 pc = position - ccenter;
0213   Vector3 pcXcd = pc.cross(caxis);
0214   Vector3 ldXcd = direction.cross(caxis);
0215   double a = ldXcd.dot(ldXcd);
0216   double b = 2. * (ldXcd.dot(pcXcd));
0217   double c = pcXcd.dot(pcXcd) - (R * R);
0218   // And solve the qaudratic equation
0219   return detail::RealQuadraticEquation(a, b, c);
0220 }
0221 
0222 SurfaceMultiIntersection CylinderSurface::intersect(
0223     const GeometryContext& gctx, const Vector3& position,
0224     const Vector3& direction, const BoundaryTolerance& boundaryTolerance,
0225     double tolerance) const {
0226   const auto& gctxTransform = transform(gctx);
0227 
0228   // Solve the quadratic equation
0229   auto qe = intersectionSolver(gctxTransform, position, direction);
0230 
0231   // If no valid solution return a non-valid surfaceIntersection
0232   if (qe.solutions == 0) {
0233     return {{Intersection3D::invalid(), Intersection3D::invalid()}, this};
0234   }
0235 
0236   // Check the validity of the first solution
0237   Vector3 solution1 = position + qe.first * direction;
0238   IntersectionStatus status1 = std::abs(qe.first) < std::abs(tolerance)
0239                                    ? IntersectionStatus::onSurface
0240                                    : IntersectionStatus::reachable;
0241 
0242   // Helper method for boundary check
0243   auto boundaryCheck = [&](const Vector3& solution,
0244                            IntersectionStatus status) -> IntersectionStatus {
0245     // No check to be done, return current status
0246     if (boundaryTolerance.isInfinite()) {
0247       return status;
0248     }
0249     const auto& cBounds = bounds();
0250     if (auto absoluteBound = boundaryTolerance.asAbsoluteBoundOpt();
0251         absoluteBound.has_value() && cBounds.coversFullAzimuth()) {
0252       // Project out the current Z value via local z axis
0253       // Built-in local to global for speed reasons
0254       const auto& tMatrix = gctxTransform.matrix();
0255       // Create the reference vector in local
0256       const Vector3 vecLocal(solution - tMatrix.block<3, 1>(0, 3));
0257       double cZ = vecLocal.dot(tMatrix.block<3, 1>(0, 2));
0258       double modifiedTolerance = tolerance + absoluteBound->tolerance1;
0259       double hZ = cBounds.get(CylinderBounds::eHalfLengthZ) + modifiedTolerance;
0260       return std::abs(cZ) < std::abs(hZ) ? status
0261                                          : IntersectionStatus::unreachable;
0262     }
0263     return isOnSurface(gctx, solution, direction, boundaryTolerance)
0264                ? status
0265                : IntersectionStatus::unreachable;
0266   };
0267   // Check first solution for boundary compatibility
0268   status1 = boundaryCheck(solution1, status1);
0269   // Set the intersection
0270   Intersection3D first(solution1, qe.first, status1);
0271   if (qe.solutions == 1) {
0272     return {{first, first}, this};
0273   }
0274   // Check the validity of the second solution
0275   Vector3 solution2 = position + qe.second * direction;
0276   IntersectionStatus status2 = std::abs(qe.second) < std::abs(tolerance)
0277                                    ? IntersectionStatus::onSurface
0278                                    : IntersectionStatus::reachable;
0279   // Check first solution for boundary compatibility
0280   status2 = boundaryCheck(solution2, status2);
0281   Intersection3D second(solution2, qe.second, status2);
0282   // Order based on path length
0283   if (first.pathLength() <= second.pathLength()) {
0284     return {{first, second}, this};
0285   }
0286   return {{second, first}, this};
0287 }
0288 
0289 AlignmentToPathMatrix CylinderSurface::alignmentToPathDerivative(
0290     const GeometryContext& gctx, const Vector3& position,
0291     const Vector3& direction) const {
0292   assert(isOnSurface(gctx, position, direction, BoundaryTolerance::Infinite()));
0293 
0294   // The vector between position and center
0295   const auto pcRowVec = (position - center(gctx)).transpose().eval();
0296   // The rotation
0297   const auto& rotation = transform(gctx).rotation();
0298   // The local frame x/y/z axis
0299   const auto& localXAxis = rotation.col(0);
0300   const auto& localYAxis = rotation.col(1);
0301   const auto& localZAxis = rotation.col(2);
0302   // The local coordinates
0303   const auto localPos = (rotation.transpose() * position).eval();
0304   const auto dx = direction.dot(localXAxis);
0305   const auto dy = direction.dot(localYAxis);
0306   const auto dz = direction.dot(localZAxis);
0307   // The normalization factor
0308   const auto norm = 1 / (1 - dz * dz);
0309   // The direction transpose
0310   const auto& dirRowVec = direction.transpose();
0311   // The derivative of path w.r.t. the local axes
0312   // @note The following calculations assume that the intersection of the track
0313   // with the cylinder always satisfy: perp(localPos) = R
0314   const auto localXAxisToPath =
0315       (-2 * norm * (dx * pcRowVec + localPos.x() * dirRowVec)).eval();
0316   const auto localYAxisToPath =
0317       (-2 * norm * (dy * pcRowVec + localPos.y() * dirRowVec)).eval();
0318   const auto localZAxisToPath =
0319       (-4 * norm * norm * (dx * localPos.x() + dy * localPos.y()) * dz *
0320        dirRowVec)
0321           .eval();
0322   // Calculate the derivative of local frame axes w.r.t its rotation
0323   const auto [rotToLocalXAxis, rotToLocalYAxis, rotToLocalZAxis] =
0324       detail::rotationToLocalAxesDerivative(rotation);
0325   // Initialize the derivative of propagation path w.r.t. local frame
0326   // translation (origin) and rotation
0327   AlignmentToPathMatrix alignToPath = AlignmentToPathMatrix::Zero();
0328   alignToPath.segment<3>(eAlignmentCenter0) =
0329       2 * norm * (dx * localXAxis.transpose() + dy * localYAxis.transpose());
0330   alignToPath.segment<3>(eAlignmentRotation0) =
0331       localXAxisToPath * rotToLocalXAxis + localYAxisToPath * rotToLocalYAxis +
0332       localZAxisToPath * rotToLocalZAxis;
0333 
0334   return alignToPath;
0335 }
0336 
0337 ActsMatrix<2, 3> CylinderSurface::localCartesianToBoundLocalDerivative(
0338     const GeometryContext& gctx, const Vector3& position) const {
0339   using VectorHelpers::perp;
0340   using VectorHelpers::phi;
0341   // The local frame transform
0342   const auto& sTransform = transform(gctx);
0343   // calculate the transformation to local coordinates
0344   const Vector3 localPos = sTransform.inverse() * position;
0345   const double lr = perp(localPos);
0346   const double lphi = phi(localPos);
0347   const double lcphi = std::cos(lphi);
0348   const double lsphi = std::sin(lphi);
0349   // Solve for radius R
0350   double R = bounds().get(CylinderBounds::eR);
0351   ActsMatrix<2, 3> loc3DToLocBound = ActsMatrix<2, 3>::Zero();
0352   loc3DToLocBound << -R * lsphi / lr, R * lcphi / lr, 0, 0, 0, 1;
0353 
0354   return loc3DToLocBound;
0355 }
0356 
0357 std::pair<std::shared_ptr<CylinderSurface>, bool> CylinderSurface::mergedWith(
0358     const CylinderSurface& other, AxisDirection direction,
0359     bool externalRotation, const Logger& logger) const {
0360   using namespace UnitLiterals;
0361 
0362   ACTS_VERBOSE("Merging cylinder surfaces in " << axisDirectionName(direction)
0363                                                << " direction");
0364 
0365   if (m_associatedDetElement != nullptr ||
0366       other.m_associatedDetElement != nullptr) {
0367     throw SurfaceMergingException(getSharedPtr(), other.getSharedPtr(),
0368                                   "CylinderSurface::merge: surfaces are "
0369                                   "associated with a detector element");
0370   }
0371 
0372   assert(m_transform != nullptr && other.m_transform != nullptr);
0373 
0374   Transform3 otherLocal = m_transform->inverse() * *other.m_transform;
0375 
0376   constexpr auto tolerance = s_onSurfaceTolerance;
0377 
0378   // surface cannot have any relative rotation
0379 
0380   if (std::abs(otherLocal.linear().col(eX)[eZ]) >= tolerance ||
0381       std::abs(otherLocal.linear().col(eY)[eZ]) >= tolerance) {
0382     ACTS_ERROR("CylinderSurface::merge: surfaces have relative rotation");
0383     throw SurfaceMergingException(
0384         getSharedPtr(), other.getSharedPtr(),
0385         "CylinderSurface::merge: surfaces have relative rotation");
0386   }
0387 
0388   auto checkNoBevel = [this, &logger, &other](const auto& bounds) {
0389     if (bounds.get(CylinderBounds::eBevelMinZ) != 0.0) {
0390       ACTS_ERROR(
0391           "CylinderVolumeStack requires all volumes to have a bevel angle of "
0392           "0");
0393       throw SurfaceMergingException(
0394           getSharedPtr(), other.getSharedPtr(),
0395           "CylinderVolumeStack requires all volumes to have a bevel angle of "
0396           "0");
0397     }
0398 
0399     if (bounds.get(CylinderBounds::eBevelMaxZ) != 0.0) {
0400       ACTS_ERROR(
0401           "CylinderVolumeStack requires all volumes to have a bevel angle of "
0402           "0");
0403       throw SurfaceMergingException(
0404           getSharedPtr(), other.getSharedPtr(),
0405           "CylinderVolumeStack requires all volumes to have a bevel angle of "
0406           "0");
0407     }
0408   };
0409 
0410   checkNoBevel(bounds());
0411   checkNoBevel(other.bounds());
0412 
0413   // radii need to be identical
0414   if (std::abs(bounds().get(CylinderBounds::eR) -
0415                other.bounds().get(CylinderBounds::eR)) > tolerance) {
0416     ACTS_ERROR("CylinderSurface::merge: surfaces have different radii");
0417     throw SurfaceMergingException(
0418         getSharedPtr(), other.getSharedPtr(),
0419         "CylinderSurface::merge: surfaces have different radii");
0420   }
0421 
0422   double r = bounds().get(CylinderBounds::eR);
0423 
0424   // no translation in x/z is allowed
0425   Vector3 translation = otherLocal.translation();
0426 
0427   if (std::abs(translation[0]) > tolerance ||
0428       std::abs(translation[1]) > tolerance) {
0429     ACTS_ERROR(
0430         "CylinderSurface::merge: surfaces have relative translation in x/y");
0431     throw SurfaceMergingException(
0432         getSharedPtr(), other.getSharedPtr(),
0433         "CylinderSurface::merge: surfaces have relative translation in x/y");
0434   }
0435 
0436   double hlZ = bounds().get(CylinderBounds::eHalfLengthZ);
0437   double minZ = -hlZ;
0438   double maxZ = hlZ;
0439 
0440   double zShift = translation[2];
0441   double otherHlZ = other.bounds().get(CylinderBounds::eHalfLengthZ);
0442   double otherMinZ = -otherHlZ + zShift;
0443   double otherMaxZ = otherHlZ + zShift;
0444 
0445   double hlPhi = bounds().get(CylinderBounds::eHalfPhiSector);
0446   double avgPhi = bounds().get(CylinderBounds::eAveragePhi);
0447 
0448   double otherHlPhi = other.bounds().get(CylinderBounds::eHalfPhiSector);
0449   double otherAvgPhi = other.bounds().get(CylinderBounds::eAveragePhi);
0450 
0451   if (direction == AxisDirection::AxisZ) {
0452     // z shift must match the bounds
0453 
0454     if (std::abs(otherLocal.linear().col(eY)[eX]) >= tolerance &&
0455         (!bounds().coversFullAzimuth() ||
0456          !other.bounds().coversFullAzimuth())) {
0457       throw SurfaceMergingException(getSharedPtr(), other.getSharedPtr(),
0458                                     "CylinderSurface::merge: surfaces have "
0459                                     "relative rotation in z and phi sector");
0460     }
0461 
0462     ACTS_VERBOSE("this: [" << minZ << ", " << maxZ << "] ~> "
0463                            << (minZ + maxZ) / 2.0 << " +- " << hlZ);
0464     ACTS_VERBOSE("zShift: " << zShift);
0465 
0466     ACTS_VERBOSE("other: [" << otherMinZ << ", " << otherMaxZ << "] ~> "
0467                             << (otherMinZ + otherMaxZ) / 2.0 << " +- "
0468                             << otherHlZ);
0469     if (std::abs(maxZ - otherMinZ) > tolerance &&
0470         std::abs(minZ - otherMaxZ) > tolerance) {
0471       ACTS_ERROR("CylinderSurface::merge: surfaces have incompatible z bounds");
0472       throw SurfaceMergingException(
0473           getSharedPtr(), other.getSharedPtr(),
0474           "CylinderSurface::merge: surfaces have incompatible z bounds");
0475     }
0476 
0477     if (hlPhi != otherHlPhi || avgPhi != otherAvgPhi) {
0478       throw SurfaceMergingException(getSharedPtr(), other.getSharedPtr(),
0479                                     "CylinderSurface::merge: surfaces have "
0480                                     "different phi sectors");
0481     }
0482 
0483     double newMaxZ = std::max(maxZ, otherMaxZ);
0484     double newMinZ = std::min(minZ, otherMinZ);
0485     double newHlZ = (newMaxZ - newMinZ) / 2.0;
0486     double newMidZ = (newMaxZ + newMinZ) / 2.0;
0487     ACTS_VERBOSE("merged: [" << newMinZ << ", " << newMaxZ << "] ~> " << newMidZ
0488                              << " +- " << newHlZ);
0489 
0490     auto newBounds = std::make_shared<CylinderBounds>(r, newHlZ, hlPhi, avgPhi);
0491 
0492     Transform3 newTransform =
0493         *m_transform * Translation3{Vector3::UnitZ() * newMidZ};
0494 
0495     return {Surface::makeShared<CylinderSurface>(newTransform, newBounds),
0496             zShift < 0};
0497 
0498   } else if (direction == AxisDirection::AxisRPhi) {
0499     // no z shift is allowed
0500     if (std::abs(translation[2]) > tolerance) {
0501       ACTS_ERROR(
0502           "CylinderSurface::merge: surfaces have relative translation in z for "
0503           "rPhi merging");
0504       throw SurfaceMergingException(
0505           getSharedPtr(), other.getSharedPtr(),
0506           "CylinderSurface::merge: surfaces have relative translation in z for "
0507           "rPhi merging");
0508     }
0509 
0510     if (hlZ != otherHlZ) {
0511       throw SurfaceMergingException(getSharedPtr(), other.getSharedPtr(),
0512                                     "CylinderSurface::merge: surfaces have "
0513                                     "different z bounds");
0514     }
0515 
0516     // Figure out signed relative rotation
0517     Vector2 rotatedX = otherLocal.linear().col(eX).head<2>();
0518     double zrotation = std::atan2(rotatedX[1], rotatedX[0]);
0519 
0520     ACTS_VERBOSE("this:  [" << avgPhi / 1_degree << " +- " << hlPhi / 1_degree
0521                             << "]");
0522     ACTS_VERBOSE("other: [" << otherAvgPhi / 1_degree << " +- "
0523                             << otherHlPhi / 1_degree << "]");
0524 
0525     ACTS_VERBOSE("Relative rotation around local z: " << zrotation / 1_degree);
0526 
0527     double prevOtherAvgPhi = otherAvgPhi;
0528     otherAvgPhi = detail::radian_sym(otherAvgPhi + zrotation);
0529     ACTS_VERBOSE("~> local other average phi: "
0530                  << otherAvgPhi / 1_degree
0531                  << " (was: " << prevOtherAvgPhi / 1_degree << ")");
0532 
0533     try {
0534       auto [newHlPhi, newAvgPhi, reversed] = detail::mergedPhiSector(
0535           hlPhi, avgPhi, otherHlPhi, otherAvgPhi, logger, tolerance);
0536 
0537       Transform3 newTransform = *m_transform;
0538 
0539       if (externalRotation) {
0540         ACTS_VERBOSE("Modifying transform for external rotation of "
0541                      << newAvgPhi / 1_degree);
0542         newTransform = newTransform * AngleAxis3(newAvgPhi, Vector3::UnitZ());
0543         newAvgPhi = 0.;
0544       }
0545 
0546       auto newBounds = std::make_shared<CylinderBounds>(
0547           r, bounds().get(CylinderBounds::eHalfLengthZ), newHlPhi, newAvgPhi);
0548 
0549       return {Surface::makeShared<CylinderSurface>(newTransform, newBounds),
0550               reversed};
0551     } catch (const std::invalid_argument& e) {
0552       throw SurfaceMergingException(getSharedPtr(), other.getSharedPtr(),
0553                                     e.what());
0554     }
0555   } else {
0556     throw SurfaceMergingException(getSharedPtr(), other.getSharedPtr(),
0557                                   "CylinderSurface::merge: invalid direction " +
0558                                       axisDirectionName(direction));
0559   }
0560 }
0561 
0562 }  // namespace Acts