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