Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-20 08:19:54

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/unit_test.hpp>
0010 
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Definitions/Units.hpp"
0013 #include "Acts/Geometry/GeometryContext.hpp"
0014 #include "Acts/SpacePointFormation/SpacePointFormationError.hpp"
0015 #include "Acts/SpacePointFormation/StripSpacePointBuilder.hpp"
0016 #include "Acts/Surfaces/PlaneSurface.hpp"
0017 #include "Acts/Surfaces/RectangleBounds.hpp"
0018 #include "ActsTests/CommonHelpers/FloatComparisons.hpp"
0019 
0020 #include <array>
0021 #include <cmath>
0022 #include <memory>
0023 #include <numbers>
0024 
0025 using namespace Acts;
0026 using namespace Acts::UnitLiterals;
0027 
0028 namespace ActsTests {
0029 
0030 namespace {
0031 
0032 const GeometryContext gctx = GeometryContext::dangerouslyDefaultConstruct();
0033 
0034 // two different strip pitches, so that the two directions cannot be confused
0035 constexpr double var1 = 4.7e-4;
0036 constexpr double var2 = 9.0e-4;
0037 
0038 StripSpacePointBuilder::StripEnds makeStrip(const Vector3& center,
0039                                             const Vector3& direction,
0040                                             double length) {
0041   const Vector3 half = 0.5 * length * direction.normalized();
0042   return {center + half, center - half};
0043 }
0044 
0045 /// A plane whose local axes are given explicitly, centred at @p center
0046 std::shared_ptr<PlaneSurface> makePlane(const Vector3& center,
0047                                         const Vector3& loc0,
0048                                         const Vector3& loc1) {
0049   Transform3 transform = Transform3::Identity();
0050   transform.matrix().block<3, 1>(0, 0) = loc0.normalized();
0051   transform.matrix().block<3, 1>(0, 1) = loc1.normalized();
0052   transform.matrix().block<3, 1>(0, 2) = loc0.cross(loc1).normalized();
0053   transform.matrix().block<3, 1>(0, 3) = center;
0054   return Surface::makeShared<PlaneSurface>(
0055       transform, std::make_shared<RectangleBounds>(50_mm, 50_mm));
0056 }
0057 
0058 /// The two local variances of a strip crossing, from the information matrix of
0059 /// the two measurements built and inverted here
0060 Vector2 referenceVariances(const double theta) {
0061   const Vector2 n1(1, 0);
0062   const Vector2 n2(std::cos(theta), std::sin(theta));
0063   const SquareMatrix2 information =
0064       n1 * n1.transpose() / var1 + n2 * n2.transpose() / var2;
0065   return information.inverse().diagonal();
0066 }
0067 
0068 /// A barrel module, whose only direction reaching `varianceZ` is the one
0069 /// along z: pointing loc0 and then loc1 at it reads the two out one at a time
0070 Vector2 varianceZR(const bool precisionAlongZ, const double theta) {
0071   const double phi = 0.4;
0072   const double r = 400_mm;
0073   const Vector3 position(r * std::cos(phi), r * std::sin(phi), 300_mm);
0074   const Vector3 rPhi(-std::sin(phi), std::cos(phi), 0);
0075   const Vector3 z = Vector3::UnitZ();
0076 
0077   auto surface = precisionAlongZ ? makePlane(position, z, rPhi)
0078                                  : makePlane(position, rPhi, z);
0079   return StripSpacePointBuilder::computeVarianceZR(gctx, *surface, position,
0080                                                    var1, var2, theta);
0081 }
0082 
0083 // orthogonal strips and the ITk strip stereo angle
0084 constexpr std::array<double, 3> testAngles{std::numbers::pi / 2, 0.4, 40e-3};
0085 
0086 /// Stereo angle between the two strip layers, as in a barrel strip module.
0087 constexpr double stereoAngle = 0.04;
0088 
0089 /// Inner strip: at x = 100, along z, centred on the x axis.
0090 StripSpacePointBuilder::StripEnds innerStrip(double halfLength) {
0091   return {Vector3(100, 0, halfLength), Vector3(100, 0, -halfLength)};
0092 }
0093 
0094 /// Outer strip: at x = 110, tilted by the stereo angle, centred on
0095 /// (110, y, z). With a vertex at the origin this gives
0096 ///   m = (10 / (11 * halfLengthInner)) * (z - y / tan(stereoAngle))
0097 ///   n = -y / (halfLengthOuter * sin(stereoAngle))
0098 StripSpacePointBuilder::StripEnds outerStrip(double y, double z,
0099                                              double halfLength) {
0100   const Vector3 centre(110, y, z);
0101   const Vector3 dir(0, std::sin(stereoAngle), std::cos(stereoAngle));
0102   return {centre + halfLength * dir, centre - halfLength * dir};
0103 }
0104 
0105 /// Place the outer strip so that the space point parameters take the requested
0106 /// values. Inverts the two relations above.
0107 StripSpacePointBuilder::StripEnds outerStripFor(double m, double n,
0108                                                 double halfLengthInner,
0109                                                 double halfLengthOuter) {
0110   const double y = -n * halfLengthOuter * std::sin(stereoAngle);
0111   const double z =
0112       m * (11. * halfLengthInner) / 10. + y / std::tan(stereoAngle);
0113   return outerStrip(y, z, halfLengthOuter);
0114 }
0115 
0116 }  // namespace
0117 
0118 BOOST_AUTO_TEST_SUITE(StripSpacePointBuilderSuite)
0119 
0120 /// Two orthogonal unit length strips with an analytically known answer.
0121 BOOST_AUTO_TEST_CASE(CosmicOrthogonalUnitStrips) {
0122   const StripSpacePointBuilder::StripEnds strip1{Vector3(0, 0, 0),
0123                                                  Vector3(-1, 0, 0)};
0124   const StripSpacePointBuilder::StripEnds strip2{Vector3(-0.5, 0.5, 1),
0125                                                  Vector3(-0.5, -0.5, 1)};
0126 
0127   const StripSpacePointBuilder::CosmicOptions options;
0128   const auto result =
0129       StripSpacePointBuilder::computeCosmicSpacePoint(strip1, strip2, options);
0130 
0131   BOOST_REQUIRE(result.ok());
0132   // The second strip runs along y at x = -0.5, so the closest point on the
0133   // first strip is at x = -0.5.
0134   CHECK_CLOSE_ABS(*result, Vector3(-0.5, 0, 0), 1e-9);
0135 }
0136 
0137 /// The closest approach is defined by the connecting vector being perpendicular
0138 /// to both strips. Checked on a realistic module pair: long strips with a small
0139 /// stereo angle, which is where a missing normalisation shows up most strongly.
0140 BOOST_AUTO_TEST_CASE(CosmicPerpendicularityRealisticStereoPair) {
0141   const double stereo = 26_mrad;
0142   const StripSpacePointBuilder::StripEnds strip1 =
0143       makeStrip(Vector3(0, 0, 0), Vector3(0, 1, 0), 50_mm);
0144   const StripSpacePointBuilder::StripEnds strip2 =
0145       makeStrip(Vector3(0.3_mm, 0, 1_mm),
0146                 Vector3(std::sin(stereo), std::cos(stereo), 0), 50_mm);
0147 
0148   const StripSpacePointBuilder::CosmicOptions options;
0149   const auto onFirst =
0150       StripSpacePointBuilder::computeCosmicSpacePoint(strip1, strip2, options);
0151   const auto onSecond =
0152       StripSpacePointBuilder::computeCosmicSpacePoint(strip2, strip1, options);
0153 
0154   BOOST_REQUIRE(onFirst.ok());
0155   BOOST_REQUIRE(onSecond.ok());
0156 
0157   const Vector3 connection = *onFirst - *onSecond;
0158   const Vector3 dir1 = (strip1.top - strip1.bottom).normalized();
0159   const Vector3 dir2 = (strip2.top - strip2.bottom).normalized();
0160 
0161   CHECK_SMALL(connection.dot(dir1), 1e-9);
0162   CHECK_SMALL(connection.dot(dir2), 1e-9);
0163   // Both points have to stay on their strip
0164   BOOST_CHECK_LE(std::abs((*onFirst).y()), 25_mm);
0165   BOOST_CHECK_LE(std::abs((*onSecond).y()), 25_mm);
0166 }
0167 
0168 /// The result must scale with the strips, i.e. not depend on the length unit.
0169 BOOST_AUTO_TEST_CASE(CosmicScaleInvariance) {
0170   const StripSpacePointBuilder::StripEnds strip1{Vector3(0, 0, 0),
0171                                                  Vector3(-1, 0, 0)};
0172   const StripSpacePointBuilder::StripEnds strip2{Vector3(-0.5, 0.5, 1),
0173                                                  Vector3(-0.5, -0.5, 1)};
0174 
0175   constexpr double scale = 50;
0176   const StripSpacePointBuilder::StripEnds scaled1{scale * strip1.top,
0177                                                   scale * strip1.bottom};
0178   const StripSpacePointBuilder::StripEnds scaled2{scale * strip2.top,
0179                                                   scale * strip2.bottom};
0180 
0181   const StripSpacePointBuilder::CosmicOptions options;
0182   const auto result =
0183       StripSpacePointBuilder::computeCosmicSpacePoint(strip1, strip2, options);
0184   const auto scaledResult = StripSpacePointBuilder::computeCosmicSpacePoint(
0185       scaled1, scaled2, options);
0186 
0187   BOOST_REQUIRE(result.ok());
0188   BOOST_REQUIRE(scaledResult.ok());
0189   CHECK_CLOSE_ABS(*scaledResult, (scale * (*result)).eval(), 1e-6);
0190 }
0191 
0192 /// Two strips crossing at a known point, separated only along z.
0193 BOOST_AUTO_TEST_CASE(CosmicCrossingStrips) {
0194   const StripSpacePointBuilder::StripEnds strip1 =
0195       makeStrip(Vector3(0, 0, 0), Vector3(0, 1, 0), 50_mm);
0196   const StripSpacePointBuilder::StripEnds strip2 =
0197       makeStrip(Vector3(0, 0, 2_mm), Vector3(1, 0, 0), 50_mm);
0198 
0199   const StripSpacePointBuilder::CosmicOptions options;
0200   const auto onFirst =
0201       StripSpacePointBuilder::computeCosmicSpacePoint(strip1, strip2, options);
0202   const auto onSecond =
0203       StripSpacePointBuilder::computeCosmicSpacePoint(strip2, strip1, options);
0204 
0205   BOOST_REQUIRE(onFirst.ok());
0206   BOOST_REQUIRE(onSecond.ok());
0207   CHECK_CLOSE_ABS(*onFirst, Vector3(0, 0, 0), 1e-9);
0208   CHECK_CLOSE_ABS(*onSecond, Vector3(0, 0, 2_mm), 1e-9);
0209 }
0210 
0211 /// A crossing beyond the end of the first strip is not a valid space point.
0212 BOOST_AUTO_TEST_CASE(CosmicCrossingOffFirstStripRejected) {
0213   const StripSpacePointBuilder::StripEnds strip1{Vector3(0, 0, 0),
0214                                                  Vector3(-1, 0, 0)};
0215   // The strips cross at x = 0.5, half a strip length past the top end
0216   const StripSpacePointBuilder::StripEnds strip2{Vector3(0.5, 0.5, 1),
0217                                                  Vector3(0.5, -0.5, 1)};
0218 
0219   const StripSpacePointBuilder::CosmicOptions options;
0220   const auto result =
0221       StripSpacePointBuilder::computeCosmicSpacePoint(strip1, strip2, options);
0222 
0223   BOOST_REQUIRE(!result.ok());
0224   BOOST_CHECK(result.error() == SpacePointFormationError::OutsideLimits);
0225 }
0226 
0227 /// The crossing has to lie on the second strip as well, even though the space
0228 /// point is reported on the first one.
0229 BOOST_AUTO_TEST_CASE(CosmicCrossingOffSecondStripRejected) {
0230   const StripSpacePointBuilder::StripEnds strip1 =
0231       makeStrip(Vector3(0, 0, 0), Vector3(1, 0, 0), 50_mm);
0232   // Crosses the first strip at its centre, but far off its own ends
0233   const StripSpacePointBuilder::StripEnds strip2 =
0234       makeStrip(Vector3(0, 100_mm, 1_mm), Vector3(0, 1, 0), 50_mm);
0235 
0236   const StripSpacePointBuilder::CosmicOptions options;
0237   const auto result =
0238       StripSpacePointBuilder::computeCosmicSpacePoint(strip1, strip2, options);
0239 
0240   BOOST_REQUIRE(!result.ok());
0241   BOOST_CHECK(result.error() == SpacePointFormationError::OutsideLimits);
0242 }
0243 
0244 /// The tolerance is a fraction of the strip length, so a crossing just past the
0245 /// end is still accepted while a larger overshoot is not.
0246 BOOST_AUTO_TEST_CASE(CosmicStripLengthTolerance) {
0247   const StripSpacePointBuilder::CosmicOptions options;
0248   const double halfLength = 25_mm;
0249 
0250   const StripSpacePointBuilder::StripEnds strip1 =
0251       makeStrip(Vector3(0, 0, 0), Vector3(1, 0, 0), 2 * halfLength);
0252 
0253   // Overshoots of half and twice the tolerance
0254   for (const double overshoot :
0255        {0.5 * options.stripLengthTolerance, 2 * options.stripLengthTolerance}) {
0256     const double x = (1 + overshoot) * halfLength;
0257     const StripSpacePointBuilder::StripEnds strip2 =
0258         makeStrip(Vector3(x, 0, 1_mm), Vector3(0, 1, 0), 50_mm);
0259 
0260     const auto result = StripSpacePointBuilder::computeCosmicSpacePoint(
0261         strip1, strip2, options);
0262 
0263     if (overshoot < options.stripLengthTolerance) {
0264       BOOST_REQUIRE(result.ok());
0265       CHECK_CLOSE_ABS(*result, Vector3(x, 0, 0), 1e-9);
0266     } else {
0267       BOOST_REQUIRE(!result.ok());
0268       BOOST_CHECK(result.error() == SpacePointFormationError::OutsideLimits);
0269     }
0270   }
0271 }
0272 
0273 /// A degenerate strip of zero length has no direction to cross with.
0274 BOOST_AUTO_TEST_CASE(CosmicZeroLengthStripRejected) {
0275   const StripSpacePointBuilder::StripEnds strip1{Vector3(0, 0, 0),
0276                                                  Vector3(0, 0, 0)};
0277   const StripSpacePointBuilder::StripEnds strip2 =
0278       makeStrip(Vector3(0, 0, 1_mm), Vector3(0, 1, 0), 50_mm);
0279 
0280   const StripSpacePointBuilder::CosmicOptions options;
0281   const auto result =
0282       StripSpacePointBuilder::computeCosmicSpacePoint(strip1, strip2, options);
0283 
0284   BOOST_REQUIRE(!result.ok());
0285   BOOST_CHECK(result.error() ==
0286               SpacePointFormationError::CosmicToleranceNotMet);
0287 }
0288 
0289 /// Parallel strips have no well defined closest approach and are rejected,
0290 /// independently of how long the strips are.
0291 BOOST_AUTO_TEST_CASE(CosmicParallelStripsRejected) {
0292   const StripSpacePointBuilder::CosmicOptions options;
0293 
0294   for (const double length : {1_mm, 50_mm, 500_mm}) {
0295     const StripSpacePointBuilder::StripEnds strip1 =
0296         makeStrip(Vector3(0, 0, 0), Vector3(0, 1, 0), length);
0297     const StripSpacePointBuilder::StripEnds strip2 =
0298         makeStrip(Vector3(0, 0, 1_mm), Vector3(0, 1, 0), length);
0299 
0300     const auto result = StripSpacePointBuilder::computeCosmicSpacePoint(
0301         strip1, strip2, options);
0302 
0303     BOOST_REQUIRE(!result.ok());
0304     BOOST_CHECK(result.error() ==
0305                 SpacePointFormationError::CosmicToleranceNotMet);
0306   }
0307 }
0308 
0309 /// A track from the origin through the centre of both strips puts the space
0310 /// point at the centre of the inner strip.
0311 BOOST_AUTO_TEST_CASE(ConstrainedCentralHit) {
0312   const StripSpacePointBuilder::StripEnds first = innerStrip(30);
0313   const StripSpacePointBuilder::StripEnds second =
0314       outerStripFor(0., 0., 30, 30);
0315 
0316   const StripSpacePointBuilder::ConstrainedOptions options;
0317   const Result<Vector3> sp =
0318       StripSpacePointBuilder::computeConstrainedSpacePoint(first, second,
0319                                                            options);
0320 
0321   BOOST_REQUIRE(sp.ok());
0322   CHECK_CLOSE_ABS(sp->x(), 100., 1e-6);
0323   CHECK_CLOSE_ABS(sp->z(), 0., 1e-6);
0324 }
0325 
0326 /// Well inside both strips: accepted, and the space point sits at m along the
0327 /// inner strip.
0328 BOOST_AUTO_TEST_CASE(ConstrainedInsideBothStrips) {
0329   const StripSpacePointBuilder::StripEnds first = innerStrip(30);
0330   const StripSpacePointBuilder::StripEnds second =
0331       outerStripFor(0.5, -0.3, 30, 30);
0332 
0333   const StripSpacePointBuilder::ConstrainedOptions options;
0334   const Result<Vector3> sp =
0335       StripSpacePointBuilder::computeConstrainedSpacePoint(first, second,
0336                                                            options);
0337 
0338   BOOST_REQUIRE(sp.ok());
0339   CHECK_CLOSE_ABS(sp->z(), 0.5 * 30., 1e-6);
0340 }
0341 
0342 /// Far outside stays rejected however generous the gap tolerance is.
0343 BOOST_AUTO_TEST_CASE(ConstrainedFarOutsideIsRejected) {
0344   const StripSpacePointBuilder::StripEnds first = innerStrip(30);
0345   const StripSpacePointBuilder::StripEnds second =
0346       outerStripFor(10., 0.2, 30, 30);
0347 
0348   StripSpacePointBuilder::ConstrainedOptions options;
0349   options.stripLengthGapTolerance = 5.;
0350 
0351   const Result<Vector3> sp =
0352       StripSpacePointBuilder::computeConstrainedSpacePoint(first, second,
0353                                                            options);
0354 
0355   BOOST_CHECK(!sp.ok());
0356   BOOST_CHECK_EQUAL(sp.error(), SpacePointFormationError::OutsideRelaxedLimits);
0357 }
0358 
0359 /// Just beyond the end of the inner strip, with the outer one well inside.
0360 BOOST_AUTO_TEST_CASE(ConstrainedRecoversOneSidedOvershoot) {
0361   const StripSpacePointBuilder::StripEnds first = innerStrip(30);
0362   const StripSpacePointBuilder::StripEnds second =
0363       outerStripFor(1.05, -0.5, 30, 30);
0364 
0365   StripSpacePointBuilder::ConstrainedOptions options;
0366   options.stripLengthTolerance = 0.01;
0367 
0368   // No gap tolerance: correctly outside the limits.
0369   options.stripLengthGapTolerance = 0.;
0370   const Result<Vector3> strict =
0371       StripSpacePointBuilder::computeConstrainedSpacePoint(first, second,
0372                                                            options);
0373   BOOST_CHECK(!strict.ok());
0374   BOOST_CHECK_EQUAL(strict.error(),
0375                     SpacePointFormationError::OutsideRelaxedLimits);
0376 
0377   // With a gap tolerance the pair has to be recovered onto the strip end.
0378   options.stripLengthGapTolerance = 5.;
0379   const Result<Vector3> relaxed =
0380       StripSpacePointBuilder::computeConstrainedSpacePoint(first, second,
0381                                                            options);
0382   BOOST_REQUIRE(relaxed.ok());
0383   CHECK_CLOSE_ABS(relaxed->z(), 30., 1e-6);
0384 }
0385 
0386 /// The gap tolerance is a length, so a short strip gets a proportionally larger
0387 /// tolerance on its parameter than a long one.
0388 BOOST_AUTO_TEST_CASE(GapToleranceUsesEachStripLength) {
0389   const StripSpacePointBuilder::StripEnds first = innerStrip(50);
0390   const StripSpacePointBuilder::StripEnds second =
0391       outerStripFor(0., -1.05, 50, 5);
0392 
0393   StripSpacePointBuilder::ConstrainedOptions options;
0394   options.stripLengthTolerance = 0.01;
0395   options.stripLengthGapTolerance = 1.;
0396 
0397   // 1 mm is 1% of the 100 mm inner strip but 10% of the 10 mm outer strip
0398   const Result<Vector3> sp =
0399       StripSpacePointBuilder::computeConstrainedSpacePoint(first, second,
0400                                                            options);
0401   BOOST_REQUIRE(sp.ok());
0402 
0403   // Too small for either strip: rejected.
0404   options.stripLengthGapTolerance = 0.1;
0405   const Result<Vector3> tight =
0406       StripSpacePointBuilder::computeConstrainedSpacePoint(first, second,
0407                                                            options);
0408   BOOST_CHECK(!tight.ok());
0409   BOOST_CHECK_EQUAL(tight.error(),
0410                     SpacePointFormationError::OutsideRelaxedLimits);
0411 }
0412 
0413 /// After the shift both parameters still have to be on their strips.
0414 BOOST_AUTO_TEST_CASE(ConstrainedRecoveryStillChecksLimits) {
0415   const StripSpacePointBuilder::StripEnds first = innerStrip(30);
0416   const StripSpacePointBuilder::StripEnds second =
0417       outerStripFor(1.05, -1.05, 30, 30);
0418 
0419   StripSpacePointBuilder::ConstrainedOptions options;
0420   options.stripLengthTolerance = 0.01;
0421   options.stripLengthGapTolerance = 5.;
0422 
0423   const Result<Vector3> sp =
0424       StripSpacePointBuilder::computeConstrainedSpacePoint(first, second,
0425                                                            options);
0426   BOOST_CHECK(!sp.ok());
0427   BOOST_CHECK_EQUAL(sp.error(), SpacePointFormationError::OutsideLimits);
0428 }
0429 
0430 /// Strip2 adds nothing along the precision direction of strip1, which keeps
0431 /// its own variance whatever the stereo angle
0432 BOOST_AUTO_TEST_CASE(PrecisionDirection) {
0433   for (const double theta : testAngles) {
0434     BOOST_TEST_CONTEXT("theta = " << theta) {
0435       BOOST_CHECK_CLOSE(varianceZR(true, theta)[0],
0436                         referenceVariances(theta)[0], 1e-6);
0437       BOOST_CHECK_CLOSE(varianceZR(true, theta)[0], var1, 1e-6);
0438     }
0439   }
0440 }
0441 
0442 /// Along the strips the crossing is located to 1 / sin(theta) of a pitch, so
0443 /// this direction degrades as the two become parallel
0444 BOOST_AUTO_TEST_CASE(AlongStripDirection) {
0445   for (const double theta : testAngles) {
0446     BOOST_TEST_CONTEXT("theta = " << theta) {
0447       BOOST_CHECK_CLOSE(varianceZR(false, theta)[0],
0448                         referenceVariances(theta)[1], 1e-6);
0449     }
0450   }
0451 }
0452 
0453 /// Orthogonal strips measure the two local directions independently
0454 BOOST_AUTO_TEST_CASE(OrthogonalStrips) {
0455   const double theta = std::numbers::pi / 2;
0456   BOOST_CHECK_CLOSE(varianceZR(true, theta)[0], var1, 1e-6);
0457   BOOST_CHECK_CLOSE(varianceZR(false, theta)[0], var2, 1e-6);
0458 }
0459 
0460 /// A barrel module carries no radial information either way
0461 BOOST_AUTO_TEST_CASE(NoRadialVariance) {
0462   BOOST_CHECK_SMALL(varianceZR(true, 40e-3)[1], 1e-12);
0463   BOOST_CHECK_SMALL(varianceZR(false, 40e-3)[1], 1e-12);
0464 }
0465 
0466 BOOST_AUTO_TEST_SUITE_END()
0467 
0468 }  // namespace ActsTests