File indexing completed on 2026-08-20 08:19:54
0001
0002
0003
0004
0005
0006
0007
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
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
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
0059
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
0069
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
0084 constexpr std::array<double, 3> testAngles{std::numbers::pi / 2, 0.4, 40e-3};
0085
0086
0087 constexpr double stereoAngle = 0.04;
0088
0089
0090 StripSpacePointBuilder::StripEnds innerStrip(double halfLength) {
0091 return {Vector3(100, 0, halfLength), Vector3(100, 0, -halfLength)};
0092 }
0093
0094
0095
0096
0097
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
0106
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 }
0117
0118 BOOST_AUTO_TEST_SUITE(StripSpacePointBuilderSuite)
0119
0120
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
0133
0134 CHECK_CLOSE_ABS(*result, Vector3(-0.5, 0, 0), 1e-9);
0135 }
0136
0137
0138
0139
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
0164 BOOST_CHECK_LE(std::abs((*onFirst).y()), 25_mm);
0165 BOOST_CHECK_LE(std::abs((*onSecond).y()), 25_mm);
0166 }
0167
0168
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
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
0212 BOOST_AUTO_TEST_CASE(CosmicCrossingOffFirstStripRejected) {
0213 const StripSpacePointBuilder::StripEnds strip1{Vector3(0, 0, 0),
0214 Vector3(-1, 0, 0)};
0215
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
0228
0229 BOOST_AUTO_TEST_CASE(CosmicCrossingOffSecondStripRejected) {
0230 const StripSpacePointBuilder::StripEnds strip1 =
0231 makeStrip(Vector3(0, 0, 0), Vector3(1, 0, 0), 50_mm);
0232
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
0245
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
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
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
0290
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
0310
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
0327
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
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
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
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
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
0387
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
0398 const Result<Vector3> sp =
0399 StripSpacePointBuilder::computeConstrainedSpacePoint(first, second,
0400 options);
0401 BOOST_REQUIRE(sp.ok());
0402
0403
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
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
0431
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
0443
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
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
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 }