File indexing completed on 2025-01-18 09:11:29
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/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
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
0079 Vector3 CylinderSurface::referencePosition(const GeometryContext& gctx,
0080 AxisDirection aDir) const {
0081
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
0088
0089
0090
0091 return center(gctx);
0092 }
0093
0094
0095 RotationMatrix3 CylinderSurface::referenceFrame(
0096 const GeometryContext& gctx, const Vector3& position,
0097 const Vector3& ) const {
0098 RotationMatrix3 mFrame;
0099
0100
0101 Vector3 measY = rotSymmetryAxis(gctx);
0102
0103 Vector3 measDepth = normal(gctx, position);
0104
0105 Vector3 measX(measY.cross(measDepth).normalized());
0106
0107 mFrame.col(0) = measX;
0108 mFrame.col(1) = measY;
0109 mFrame.col(2) = measDepth;
0110
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
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
0133
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
0164 Vector3 pos3D = sfTransform.inverse() * position;
0165
0166 pos3D.z() = 0.;
0167
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
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
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
0204 double R = bounds().get(CylinderBounds::eR);
0205
0206
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
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
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
0229 auto qe = intersectionSolver(gctxTransform, position, direction);
0230
0231
0232 if (qe.solutions == 0) {
0233 return {{Intersection3D::invalid(), Intersection3D::invalid()}, this};
0234 }
0235
0236
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
0243 auto boundaryCheck = [&](const Vector3& solution,
0244 IntersectionStatus status) -> IntersectionStatus {
0245
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
0253
0254 const auto& tMatrix = gctxTransform.matrix();
0255
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
0268 status1 = boundaryCheck(solution1, status1);
0269
0270 Intersection3D first(solution1, qe.first, status1);
0271 if (qe.solutions == 1) {
0272 return {{first, first}, this};
0273 }
0274
0275 Vector3 solution2 = position + qe.second * direction;
0276 IntersectionStatus status2 = std::abs(qe.second) < std::abs(tolerance)
0277 ? IntersectionStatus::onSurface
0278 : IntersectionStatus::reachable;
0279
0280 status2 = boundaryCheck(solution2, status2);
0281 Intersection3D second(solution2, qe.second, status2);
0282
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
0295 const auto pcRowVec = (position - center(gctx)).transpose().eval();
0296
0297 const auto& rotation = transform(gctx).rotation();
0298
0299 const auto& localXAxis = rotation.col(0);
0300 const auto& localYAxis = rotation.col(1);
0301 const auto& localZAxis = rotation.col(2);
0302
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
0308 const auto norm = 1 / (1 - dz * dz);
0309
0310 const auto& dirRowVec = direction.transpose();
0311
0312
0313
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
0323 const auto [rotToLocalXAxis, rotToLocalYAxis, rotToLocalZAxis] =
0324 detail::rotationToLocalAxesDerivative(rotation);
0325
0326
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
0342 const auto& sTransform = transform(gctx);
0343
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
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
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
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
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
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
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
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 }