Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-10-22 07:53:35

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/TrackParametrization.hpp"
0013 #include "Acts/EventData/ParticleHypothesis.hpp"
0014 #include "Acts/EventData/TrackParameters.hpp"
0015 #include "Acts/Surfaces/PerigeeSurface.hpp"
0016 #include "Acts/Surfaces/Surface.hpp"
0017 #include "Acts/Utilities/Result.hpp"
0018 #include "Acts/Vertexing/AdaptiveGridTrackDensity.hpp"
0019 #include "ActsTests/CommonHelpers/FloatComparisons.hpp"
0020 
0021 #include <memory>
0022 #include <numbers>
0023 #include <optional>
0024 #include <utility>
0025 
0026 using namespace Acts;
0027 using namespace Acts::UnitLiterals;
0028 
0029 namespace ActsTests {
0030 
0031 using Covariance = BoundSquareMatrix;
0032 
0033 Covariance makeRandomCovariance(int seed = 31415) {
0034   std::srand(seed);
0035   Covariance randMat((Covariance::Random() + 1.5 * Covariance::Identity()) *
0036                      0.05);
0037 
0038   // symmetric covariance matrix
0039   Covariance covMat = 0.5 * (randMat + randMat.transpose());
0040 
0041   return covMat;
0042 }
0043 
0044 BOOST_AUTO_TEST_SUITE(VertexingSuite)
0045 
0046 BOOST_AUTO_TEST_CASE(compare_to_analytical_solution_for_single_track) {
0047   // Using a large track grid so we can choose a small bin size
0048   const std::uint32_t spatialTrkGridSize = 4001;
0049   // Arbitrary (but small) bin size
0050   const double binExtent = 3.1e-4;
0051   // Arbitrary impact parameters
0052   const double d0 = 0.4;
0053   const double z0 = -0.2;
0054   Vector2 impactParameters{d0, z0};
0055 
0056   Covariance covMat = makeRandomCovariance();
0057   SquareMatrix2 subCovMat = covMat.block<2, 2>(0, 0);
0058   BoundVector paramVec;
0059   paramVec << d0, z0, 0, 0, 0, 0;
0060 
0061   // Create perigee surface
0062   std::shared_ptr<PerigeeSurface> perigeeSurface =
0063       Surface::makeShared<PerigeeSurface>(Vector3(0., 0., 0.));
0064 
0065   BoundTrackParameters params1(perigeeSurface, paramVec, covMat,
0066                                ParticleHypothesis::pion());
0067 
0068   AdaptiveGridTrackDensity::Config cfg;
0069   // force track to have exactly spatialTrkGridSize spatial bins for testing
0070   // purposes
0071   cfg.spatialTrkGridSizeRange = {spatialTrkGridSize, spatialTrkGridSize};
0072   cfg.spatialBinExtent = binExtent;
0073   AdaptiveGridTrackDensity grid(cfg);
0074 
0075   // Empty map
0076   AdaptiveGridTrackDensity::DensityMap mainDensityMap;
0077 
0078   // Add track
0079   auto trackDensityMap = grid.addTrack(params1, mainDensityMap);
0080 
0081   double relTol = 1e-5;
0082   double small = 1e-5;
0083 
0084   auto gaussian2D = [&](const Vector2& args, const Vector2& mus,
0085                         const SquareMatrix2& sigmas) {
0086     Vector2 diffs = args - mus;
0087     double coef = 1 / std::sqrt(sigmas.determinant());
0088     double expo = -0.5 * diffs.transpose().dot(sigmas.inverse() * diffs);
0089     return coef * std::exp(expo);
0090   };
0091 
0092   for (const auto& [bin, _] : mainDensityMap) {
0093     // Extract variables for better readability
0094     std::int32_t zBin = bin.first;
0095     float density = mainDensityMap.at(bin);
0096     // Argument for 2D gaussian
0097     Vector2 dzVec{0., grid.getBinCenter(zBin, binExtent)};
0098     // Compute correct density...
0099     float correctDensity = gaussian2D(dzVec, impactParameters, subCovMat);
0100     // ... and check if our result is equivalent
0101     CHECK_CLOSE_OR_SMALL(correctDensity, density, relTol, small);
0102   }
0103 
0104   // Analytical maximum of the Gaussian (can be obtained by expressing the
0105   // exponent as (az - b)^2 + c and noting correctMaxZ = b/a)
0106   double correctMaxZ =
0107       -0.5 * (subCovMat(0, 1) + subCovMat(1, 0)) / subCovMat(0, 0) * d0 + z0;
0108   // Analytical FWHM of the Gaussian (result similar to
0109   // https://en.wikipedia.org/wiki/Full_width_at_half_maximum#Normal_distribution
0110   // but the calculation needs to be slightly modified in our case)
0111   double correctFWHM =
0112       2. * std::sqrt(2 * std::numbers::ln2 * subCovMat.determinant() /
0113                      subCovMat(0, 0));
0114 
0115   // Estimate maximum z position and seed width
0116   auto res = grid.getMaxZTPositionAndWidth(mainDensityMap);
0117   BOOST_CHECK(res.ok());
0118 
0119   // Extract variables for better readability...
0120   double maxZ = res.value().first.first;
0121   double fwhm = res.value().second * 2.355;
0122 
0123   // ... and check if they are correct (note: the optimization is not as exact
0124   // as the density values).
0125   double relTolOptimization = 1e-3;
0126   CHECK_CLOSE_REL(correctMaxZ, maxZ, relTolOptimization);
0127   CHECK_CLOSE_REL(correctFWHM, fwhm, relTolOptimization);
0128 }
0129 
0130 BOOST_AUTO_TEST_CASE(
0131     compare_to_analytical_solution_for_single_track_with_time) {
0132   // Number of bins in z- and t-direction
0133   const std::uint32_t spatialTrkGridSize = 401;
0134   const std::uint32_t temporalTrkGridSize = 401;
0135   // Bin extents
0136   const double spatialBinExtent = 3.1e-3;
0137   const double temporalBinExtent = 3.1e-3;
0138   // Arbitrary impact parameters
0139   const double d0 = -0.1;
0140   const double z0 = -0.2;
0141   const double t0 = 0.1;
0142   Vector3 impactParameters{d0, z0, t0};
0143 
0144   // symmetric covariance matrix
0145   Covariance covMat = makeRandomCovariance();
0146 
0147   BoundVector paramVec;
0148   paramVec << d0, z0, 0, 0, 0, t0;
0149   // Create perigee surface
0150   std::shared_ptr<PerigeeSurface> perigeeSurface =
0151       Surface::makeShared<PerigeeSurface>(Vector3(0., 0., 0.));
0152 
0153   BoundTrackParameters params(perigeeSurface, paramVec, covMat,
0154                               ParticleHypothesis::pion());
0155 
0156   ActsSquareMatrix<3> ipCov = params.impactParameterCovariance().value();
0157 
0158   AdaptiveGridTrackDensity::Config cfg;
0159   // force track to have exactly spatialTrkGridSize spatial bins for testing
0160   // purposes
0161   cfg.spatialTrkGridSizeRange = {spatialTrkGridSize, spatialTrkGridSize};
0162   cfg.spatialBinExtent = spatialBinExtent;
0163   // force track to have exactly temporalTrkGridSize temporal bins for testing
0164   // purposes
0165   cfg.temporalTrkGridSizeRange = {temporalTrkGridSize, temporalTrkGridSize};
0166   cfg.temporalBinExtent = temporalBinExtent;
0167   cfg.useTime = true;
0168   AdaptiveGridTrackDensity grid(cfg);
0169 
0170   // Empty map
0171   AdaptiveGridTrackDensity::DensityMap mainDensityMap;
0172 
0173   // Add track
0174   auto trackDensityMap = grid.addTrack(params, mainDensityMap);
0175 
0176   double relTol = 1e-5;
0177   double small = 1e-5;
0178 
0179   auto gaussian3D = [&](const Vector3& args, const Vector3& mus,
0180                         const SquareMatrix3& sigmas) {
0181     Vector3 diffs = args - mus;
0182     double coef = 1 / std::sqrt(sigmas.determinant());
0183     double expo = -0.5 * diffs.transpose().dot(sigmas.inverse() * diffs);
0184     return coef * std::exp(expo);
0185   };
0186 
0187   for (const auto& [bin, density] : mainDensityMap) {
0188     // Extract variables for better readability
0189     double z = grid.getBinCenter(bin.first, spatialBinExtent);
0190     double t = grid.getBinCenter(bin.second, temporalBinExtent);
0191     // Argument for 3D gaussian
0192     Vector3 dztVec{0., z, t};
0193 
0194     // Compute correct density...
0195     float correctDensity = gaussian3D(dztVec, impactParameters, ipCov);
0196 
0197     // ... and check if our result is equivalent
0198     CHECK_CLOSE_OR_SMALL(correctDensity, density, relTol, small);
0199   }
0200 
0201   // The analytical calculations of the following can be found here:
0202   // https://acts.readthedocs.io/en/latest/white_papers/gaussian-track-densities.html
0203   // Analytical maximum of the Gaussian
0204   ActsSquareMatrix<3> ipWeights = ipCov.inverse();
0205   double denom =
0206       ipWeights(1, 1) * ipWeights(2, 2) - ipWeights(1, 2) * ipWeights(1, 2);
0207 
0208   double zNom =
0209       ipWeights(0, 1) * ipWeights(2, 2) - ipWeights(0, 2) * ipWeights(1, 2);
0210   double correctMaxZ = zNom / denom * d0 + z0;
0211 
0212   double tNom =
0213       ipWeights(0, 2) * ipWeights(1, 1) - ipWeights(0, 1) * ipWeights(1, 2);
0214   double correctMaxT = tNom / denom * d0 + t0;
0215 
0216   // Analytical FWHM of the Gaussian
0217   double correctFWHM = 2. * std::sqrt(2 * std::numbers::ln2 / ipWeights(1, 1));
0218 
0219   // Estimate maximum z position and seed width
0220   auto res = grid.getMaxZTPositionAndWidth(mainDensityMap);
0221   BOOST_CHECK(res.ok());
0222 
0223   // Extract variables for better readability...
0224   double maxZ = res.value().first.first;
0225   double maxT = res.value().first.second;
0226   double fwhm = res.value().second * 2.355;
0227 
0228   // ... and check if they are correct (note: the optimization is not as exact
0229   // as the density values).
0230   double relTolOptimization = 1e-1;
0231   CHECK_CLOSE_REL(correctMaxZ, maxZ, relTolOptimization);
0232   CHECK_CLOSE_REL(correctMaxT, maxT, relTolOptimization);
0233   CHECK_CLOSE_REL(correctFWHM, fwhm, relTolOptimization);
0234 }
0235 
0236 BOOST_AUTO_TEST_CASE(seed_width_estimation) {
0237   // Dummy track grid size (not needed for this unit test)
0238   const std::uint32_t spatialTrkGridSize = 1;
0239   double binExtent = 2.;
0240   AdaptiveGridTrackDensity::Config cfg;
0241   cfg.spatialTrkGridSizeRange = {spatialTrkGridSize, spatialTrkGridSize};
0242   cfg.spatialBinExtent = binExtent;
0243   AdaptiveGridTrackDensity grid(cfg);
0244 
0245   // Empty map
0246   AdaptiveGridTrackDensity::DensityMap mainDensityMap;
0247 
0248   // z-position of the maximum density
0249   double correctMaxZ = -2.;
0250 
0251   // Fill map with a triangular track density.
0252   // We use an isoscele triangle with a maximum density value of 1 and a width
0253   // of 20 mm. The linear approximation we use during the seed width estimation
0254   // should be exact in this case.
0255   for (std::int32_t i = -6; i <= 4; i++) {
0256     mainDensityMap[{i, 0}] =
0257         1.0 - 0.1 * std::abs(correctMaxZ - grid.getBinCenter(i, binExtent));
0258   }
0259 
0260   // Get maximum z position and corresponding seed width
0261   auto res = grid.getMaxZTPositionAndWidth(mainDensityMap);
0262   BOOST_CHECK(res.ok());
0263 
0264   // Check if we found the correct maximum
0265   double maxZ = res.value().first.first;
0266   BOOST_CHECK_EQUAL(correctMaxZ, maxZ);
0267 
0268   // Calculate full width at half maximum from seed width and check if it's
0269   // correct
0270   double fwhm = res.value().second * 2.355;
0271   CHECK_CLOSE_REL(10., fwhm, 1e-5);
0272 }
0273 
0274 BOOST_AUTO_TEST_CASE(track_adding) {
0275   const std::uint32_t spatialTrkGridSize = 15;
0276 
0277   double binExtent = 0.1;  // mm
0278 
0279   AdaptiveGridTrackDensity::Config cfg;
0280   cfg.spatialTrkGridSizeRange = {spatialTrkGridSize, spatialTrkGridSize};
0281   cfg.spatialBinExtent = binExtent;
0282   AdaptiveGridTrackDensity grid(cfg);
0283 
0284   // Create some test tracks in such a way that some tracks
0285   //  e.g. overlap and that certain tracks need to be inserted
0286   // between two other tracks
0287   Covariance covMat(Covariance::Identity());
0288 
0289   BoundVector paramVec0;
0290   paramVec0 << 100.0, -0.4, 0, 0, 0, 0;
0291   BoundVector paramVec1;
0292   paramVec1 << 0.01, -0.4, 0, 0, 0, 0;
0293   BoundVector paramVec2;
0294   paramVec2 << 0.01, 10.9, 0, 0, 0, 0;
0295   BoundVector paramVec3;
0296   paramVec3 << 0.01, 0.9, 0, 0, 0, 0;
0297 
0298   // Create perigee surface
0299   std::shared_ptr<PerigeeSurface> perigeeSurface =
0300       Surface::makeShared<PerigeeSurface>(Vector3(0., 0., 0.));
0301 
0302   BoundTrackParameters params0(perigeeSurface, paramVec0, covMat,
0303                                ParticleHypothesis::pion());
0304   BoundTrackParameters params1(perigeeSurface, paramVec1, covMat,
0305                                ParticleHypothesis::pion());
0306   BoundTrackParameters params2(perigeeSurface, paramVec2, covMat,
0307                                ParticleHypothesis::pion());
0308   BoundTrackParameters params3(perigeeSurface, paramVec3, covMat,
0309                                ParticleHypothesis::pion());
0310 
0311   // Empty map
0312   AdaptiveGridTrackDensity::DensityMap mainDensityMap;
0313 
0314   // Track is too far away from z axis and was not added
0315   auto trackDensityMap = grid.addTrack(params0, mainDensityMap);
0316   BOOST_CHECK(mainDensityMap.empty());
0317 
0318   // Track should have been entirely added to both grids
0319   trackDensityMap = grid.addTrack(params1, mainDensityMap);
0320   BOOST_CHECK_EQUAL(spatialTrkGridSize, mainDensityMap.size());
0321 
0322   // Track should have been entirely added to both grids
0323   trackDensityMap = grid.addTrack(params2, mainDensityMap);
0324   BOOST_CHECK_EQUAL(2 * spatialTrkGridSize, mainDensityMap.size());
0325 
0326   // Track 3 has overlap of 2 bins with track 1
0327   trackDensityMap = grid.addTrack(params3, mainDensityMap);
0328   BOOST_CHECK_EQUAL(3 * spatialTrkGridSize - 2, mainDensityMap.size());
0329 
0330   // Add first track again, should *not* introduce new z entries
0331   trackDensityMap = grid.addTrack(params1, mainDensityMap);
0332   BOOST_CHECK_EQUAL(3 * spatialTrkGridSize - 2, mainDensityMap.size());
0333 }
0334 
0335 BOOST_AUTO_TEST_CASE(max_z_t_and_width) {
0336   const std::uint32_t spatialTrkGridSize = 29;
0337   const std::uint32_t temporalTrkGridSize = 29;
0338 
0339   // spatial and temporal bin extent
0340   double binExtent = 0.05;
0341 
0342   // 1D grid of z values
0343   AdaptiveGridTrackDensity::Config cfg1D;
0344   cfg1D.spatialTrkGridSizeRange = {spatialTrkGridSize, spatialTrkGridSize};
0345   cfg1D.spatialBinExtent = binExtent;
0346   AdaptiveGridTrackDensity grid1D(cfg1D);
0347 
0348   // 2D grid of z and t values
0349   AdaptiveGridTrackDensity::Config cfg2D;
0350   cfg2D.spatialTrkGridSizeRange = {spatialTrkGridSize, spatialTrkGridSize};
0351   cfg2D.spatialBinExtent = binExtent;
0352   cfg2D.temporalTrkGridSizeRange = {temporalTrkGridSize, temporalTrkGridSize};
0353   cfg2D.temporalBinExtent = binExtent;
0354   cfg2D.useTime = true;
0355   AdaptiveGridTrackDensity grid2D(cfg2D);
0356 
0357   // Create some test tracks
0358   Covariance covMat(Covariance::Identity() * 0.005);
0359 
0360   double z0Trk1 = 0.25;
0361   double t0Trk1 = 0.05;
0362   double z0Trk2 = -10.95;
0363   double t0Trk2 = 0.1;
0364   BoundVector paramVec1;
0365   paramVec1 << 0.02, z0Trk1, 0, 0, 0, t0Trk1;
0366   BoundVector paramVec2;
0367   paramVec2 << 0.01, z0Trk2, 0, 0, 0, t0Trk2;
0368 
0369   // Create perigee surface
0370   std::shared_ptr<PerigeeSurface> perigeeSurface =
0371       Surface::makeShared<PerigeeSurface>(Vector3(0., 0., 0.));
0372 
0373   BoundTrackParameters params1(perigeeSurface, paramVec1, covMat,
0374                                ParticleHypothesis::pion());
0375   BoundTrackParameters params2(perigeeSurface, paramVec2, covMat,
0376                                ParticleHypothesis::pion());
0377 
0378   // Empty maps
0379   AdaptiveGridTrackDensity::DensityMap mainDensityMap1D;
0380   AdaptiveGridTrackDensity::DensityMap mainDensityMap2D;
0381 
0382   // Add first track to spatial grid
0383   auto trackDensityMap = grid1D.addTrack(params1, mainDensityMap1D);
0384   auto firstRes = grid1D.getMaxZTPosition(mainDensityMap1D);
0385   BOOST_CHECK(firstRes.ok());
0386   // Maximum should be at z0Trk1 position ...
0387   BOOST_CHECK_EQUAL(z0Trk1, (*firstRes).first);
0388   // ... and the corresponding time should be set to 0
0389   BOOST_CHECK_EQUAL(0., (*firstRes).second);
0390 
0391   // Add first track to 2D grid
0392   auto trackDensityMap2D = grid2D.addTrack(params1, mainDensityMap2D);
0393   auto firstRes2D = grid2D.getMaxZTPosition(mainDensityMap2D);
0394   BOOST_CHECK(firstRes2D.ok());
0395   // Maximum should be at z0Trk1 position ...
0396   BOOST_CHECK_EQUAL(z0Trk1, (*firstRes2D).first);
0397   // ... and the corresponding time should be at t0Trk1
0398   BOOST_CHECK_EQUAL(t0Trk1, (*firstRes2D).second);
0399 
0400   // Add second track to spatial grid
0401   trackDensityMap = grid1D.addTrack(params2, mainDensityMap1D);
0402   // Calculate maximum and the corresponding width
0403   auto secondRes = grid1D.getMaxZTPositionAndWidth(mainDensityMap1D);
0404   BOOST_CHECK(secondRes.ok());
0405   // Trk 2 is closer to z-axis and should thus yield higher density values.
0406   // Therefore, the new maximum is at z0Trk2 ...
0407   CHECK_CLOSE_OR_SMALL(z0Trk2, (*secondRes).first.first, 1e-5, 1e-5);
0408   // ... the corresponding time should be set to 0...
0409   BOOST_CHECK_EQUAL(0., (*secondRes).first.second);
0410   // ... and it should have a positive width
0411   BOOST_CHECK_LT(0, (*secondRes).second);
0412 
0413   // Add second track to 2D grid
0414   trackDensityMap = grid2D.addTrack(params2, mainDensityMap2D);
0415   // Calculate maximum and the corresponding width
0416   auto secondRes2D = grid2D.getMaxZTPositionAndWidth(mainDensityMap2D);
0417   BOOST_CHECK(secondRes2D.ok());
0418   // Trk 2 is closer to z-axis and should thus yield higher density values.
0419   // Therefore, the new maximum is at z0Trk2 ...
0420   CHECK_CLOSE_OR_SMALL(z0Trk2, (*secondRes2D).first.first, 1e-5, 1e-5);
0421   // ... the corresponding time should be at t0Trk2 ...
0422   BOOST_CHECK_EQUAL(t0Trk2, (*secondRes2D).first.second);
0423   // ... and it should have approximately the same width in z direction
0424   CHECK_CLOSE_OR_SMALL((*secondRes2D).second, (*secondRes).second, 1e-5, 1e-5);
0425 }
0426 
0427 BOOST_AUTO_TEST_CASE(highest_density_sum) {
0428   const std::uint32_t spatialTrkGridSize = 29;
0429 
0430   double binExtent = 0.05;  // mm
0431 
0432   AdaptiveGridTrackDensity::Config cfg;
0433   cfg.spatialTrkGridSizeRange = {spatialTrkGridSize, spatialTrkGridSize};
0434   cfg.spatialBinExtent = binExtent;
0435   cfg.useHighestSumZPosition = true;
0436 
0437   AdaptiveGridTrackDensity grid(cfg);
0438 
0439   // Create some test tracks
0440   Covariance covMat(Covariance::Identity() * 0.005);
0441 
0442   double z0Trk1 = 0.25;
0443   double z0Trk2 = -10.95;
0444   BoundVector paramVec1;
0445   paramVec1 << 0.01, z0Trk1, 0, 0, 0, 0;
0446   BoundVector paramVec2;
0447   paramVec2 << 0.0095, z0Trk2, 0, 0, 0, 0;
0448 
0449   // Create perigee surface
0450   std::shared_ptr<PerigeeSurface> perigeeSurface =
0451       Surface::makeShared<PerigeeSurface>(Vector3(0., 0., 0.));
0452 
0453   BoundTrackParameters params1(perigeeSurface, paramVec1, covMat,
0454                                ParticleHypothesis::pion());
0455   BoundTrackParameters params2(perigeeSurface, paramVec2, covMat,
0456                                ParticleHypothesis::pion());
0457 
0458   // Empty map
0459   AdaptiveGridTrackDensity::DensityMap mainDensityMap;
0460 
0461   // Fill grid with track densities
0462   auto trackDensityMap = grid.addTrack(params1, mainDensityMap);
0463 
0464   auto res1 = grid.getMaxZTPosition(mainDensityMap);
0465   BOOST_CHECK(res1.ok());
0466   // Maximum should be at z0Trk1 position
0467   BOOST_CHECK_EQUAL(z0Trk1, (*res1).first);
0468 
0469   // Add second track
0470   trackDensityMap = grid.addTrack(params2, mainDensityMap);
0471   auto res2 = grid.getMaxZTPosition(mainDensityMap);
0472   BOOST_CHECK(res2.ok());
0473   // Trk 2 is closer to z-axis and should yield higher density values
0474   // New maximum is therefore at z0Trk2
0475   CHECK_CLOSE_REL(z0Trk2, (*res2).first, 1e-5);
0476 
0477   // Add small density values around the maximum of track 1
0478   mainDensityMap[{4, 0}] += 0.5;
0479   mainDensityMap[{6, 0}] += 0.5;
0480 
0481   auto res3 = grid.getMaxZTPosition(mainDensityMap);
0482   BOOST_CHECK(res3.ok());
0483   // Trk 2 still has the highest peak density value, however, the small
0484   // added densities for track 1 around its maximum should now lead to
0485   // a prediction at z0Trk1 again
0486   BOOST_CHECK_EQUAL(z0Trk1, (*res3).first);
0487 }
0488 
0489 BOOST_AUTO_TEST_CASE(track_removing) {
0490   const std::uint32_t spatialTrkGridSize = 29;
0491   const std::uint32_t temporalTrkGridSize = 29;
0492 
0493   std::uint32_t trkGridSize = spatialTrkGridSize * temporalTrkGridSize;
0494 
0495   // bin extent in z and t direction
0496   double binExtent = 0.05;
0497 
0498   // 1D grid
0499   AdaptiveGridTrackDensity::Config cfg1D;
0500   cfg1D.spatialTrkGridSizeRange = {spatialTrkGridSize, spatialTrkGridSize};
0501   cfg1D.spatialBinExtent = binExtent;
0502   AdaptiveGridTrackDensity grid1D(cfg1D);
0503 
0504   // 2D grid
0505   AdaptiveGridTrackDensity::Config cfg2D;
0506   cfg2D.spatialTrkGridSizeRange = {spatialTrkGridSize, spatialTrkGridSize};
0507   cfg2D.spatialBinExtent = binExtent;
0508   cfg2D.temporalTrkGridSizeRange = {temporalTrkGridSize, temporalTrkGridSize};
0509   cfg2D.temporalBinExtent = binExtent;
0510   cfg2D.useTime = true;
0511   AdaptiveGridTrackDensity grid2D(cfg2D);
0512 
0513   // Create some test tracks
0514   Covariance covMat = makeRandomCovariance();
0515 
0516   // Define z0 values for test tracks
0517   double z0Trk1 = -0.45;
0518   double t0Trk1 = -0.15;
0519   double z0Trk2 = -0.25;
0520   double t0Trk2 = t0Trk1;
0521 
0522   BoundVector paramVec0;
0523   paramVec0 << 0.1, z0Trk1, 0, 0, 0, t0Trk1;
0524   BoundVector paramVec1;
0525   paramVec1 << 0.1, z0Trk2, 0, 0, 0, t0Trk2;
0526 
0527   // Create perigee surface
0528   std::shared_ptr<PerigeeSurface> perigeeSurface =
0529       Surface::makeShared<PerigeeSurface>(Vector3(0., 0., 0.));
0530 
0531   BoundTrackParameters params0(perigeeSurface, paramVec0, covMat,
0532                                ParticleHypothesis::pion());
0533   BoundTrackParameters params1(perigeeSurface, paramVec1, covMat,
0534                                ParticleHypothesis::pion());
0535 
0536   // Empty maps
0537   AdaptiveGridTrackDensity::DensityMap mainDensityMap1D;
0538   AdaptiveGridTrackDensity::DensityMap mainDensityMap2D;
0539 
0540   // Lambda for calculating total density
0541   auto densitySum = [](const auto& densityMap) {
0542     double sum = 0.;
0543     for (const auto& [_, density] : densityMap) {
0544       sum += density;
0545     }
0546     return sum;
0547   };
0548 
0549   // Add track 0 to 1D grid
0550   auto firstTrackDensityMap1D = grid1D.addTrack(params0, mainDensityMap1D);
0551   BOOST_CHECK(!mainDensityMap1D.empty());
0552   // Grid size should match spatialTrkGridSize
0553   BOOST_CHECK_EQUAL(spatialTrkGridSize, mainDensityMap1D.size());
0554   double firstDensitySum1D = densitySum(mainDensityMap1D);
0555 
0556   // Add track 0 to 2D grid
0557   auto firstTrackDensityMap2D = grid2D.addTrack(params0, mainDensityMap2D);
0558   BOOST_CHECK(!mainDensityMap2D.empty());
0559   // Grid size should match spatialTrkGridSize
0560   BOOST_CHECK_EQUAL(trkGridSize, mainDensityMap2D.size());
0561   double firstDensitySum2D = densitySum(mainDensityMap2D);
0562 
0563   // Add track 0 again to 1D grid
0564   firstTrackDensityMap1D = grid1D.addTrack(params0, mainDensityMap1D);
0565   BOOST_CHECK(!mainDensityMap1D.empty());
0566   // Grid size should still match spatialTrkGridSize
0567   BOOST_CHECK_EQUAL(spatialTrkGridSize, mainDensityMap1D.size());
0568   // Calculate new total density ...
0569   double secondDensitySum1D = densitySum(mainDensityMap1D);
0570   // ... and check that it's twice as large as before
0571   BOOST_CHECK_EQUAL(2 * firstDensitySum1D, secondDensitySum1D);
0572 
0573   // Add track 0 again to 2D grid
0574   firstTrackDensityMap2D = grid2D.addTrack(params0, mainDensityMap2D);
0575   BOOST_CHECK(!mainDensityMap2D.empty());
0576   // Grid size should still match trkGridSize
0577   BOOST_CHECK_EQUAL(trkGridSize, mainDensityMap2D.size());
0578   // Calculate new total density ...
0579   double secondDensitySum2D = densitySum(mainDensityMap2D);
0580   // ... and check that it's twice as large as before
0581   BOOST_CHECK_EQUAL(2 * firstDensitySum2D, secondDensitySum2D);
0582 
0583   // Remove track 0 from 1D grid
0584   grid1D.subtractTrack(firstTrackDensityMap1D, mainDensityMap1D);
0585   // Calculate new total density
0586   double thirdDensitySum1D = densitySum(mainDensityMap1D);
0587   // Density should be old one again
0588   BOOST_CHECK_EQUAL(firstDensitySum1D, thirdDensitySum1D);
0589   // Grid size should still match spatialTrkGridSize (removal does not change
0590   // grid size)
0591   BOOST_CHECK_EQUAL(spatialTrkGridSize, mainDensityMap1D.size());
0592 
0593   // Remove track 0 from 2D grid
0594   grid2D.subtractTrack(firstTrackDensityMap2D, mainDensityMap2D);
0595   // Calculate new total density
0596   double thirdDensitySum2D = densitySum(mainDensityMap2D);
0597   // Density should be old one again
0598   BOOST_CHECK_EQUAL(firstDensitySum2D, thirdDensitySum2D);
0599   // Grid size should still match trkGridSize (removal does not change grid
0600   // size)
0601   BOOST_CHECK_EQUAL(trkGridSize, mainDensityMap2D.size());
0602 
0603   // Add track 1 to 1D grid (overlaps with track 0!)
0604   auto secondTrackDensityMap1D = grid1D.addTrack(params1, mainDensityMap1D);
0605   std::uint32_t nNonOverlappingBins1D =
0606       static_cast<std::uint32_t>(std::abs(z0Trk1 - z0Trk2) / binExtent);
0607   BOOST_CHECK_EQUAL(spatialTrkGridSize + nNonOverlappingBins1D,
0608                     mainDensityMap1D.size());
0609   double fourthDensitySum1D = densitySum(mainDensityMap1D);
0610 
0611   // Add track 1 to 2D grid (overlaps with track 0!)
0612   auto secondTrackDensityMap2D = grid2D.addTrack(params1, mainDensityMap2D);
0613   std::uint32_t nNonOverlappingBins2D =
0614       nNonOverlappingBins1D * temporalTrkGridSize;
0615   BOOST_CHECK_EQUAL(trkGridSize + nNonOverlappingBins2D,
0616                     mainDensityMap2D.size());
0617   double fourthDensitySum2D = densitySum(mainDensityMap2D);
0618 
0619   // Remove second track 0 from 1D grid
0620   grid1D.subtractTrack(firstTrackDensityMap1D, mainDensityMap1D);
0621   double fifthDensitySum1D = densitySum(mainDensityMap1D);
0622   // Density should match differences of removed tracks
0623   CHECK_CLOSE_REL(fifthDensitySum1D, fourthDensitySum1D - firstDensitySum1D,
0624                   1e-5);
0625 
0626   // Remove second track 0 from 2D grid
0627   grid2D.subtractTrack(firstTrackDensityMap2D, mainDensityMap2D);
0628   double fifthDensitySum2D = densitySum(mainDensityMap2D);
0629   // Density should match differences of removed tracks
0630   CHECK_CLOSE_REL(fifthDensitySum2D, fourthDensitySum2D - firstDensitySum2D,
0631                   1e-5);
0632 
0633   // Remove track 1 from 1D grid
0634   grid1D.subtractTrack(secondTrackDensityMap1D, mainDensityMap1D);
0635   // Size should not have changed
0636   BOOST_CHECK_EQUAL(spatialTrkGridSize + nNonOverlappingBins1D,
0637                     mainDensityMap1D.size());
0638   double sixthDensitySum1D = densitySum(mainDensityMap1D);
0639   // 1D grid is now empty after all tracks were removed
0640   CHECK_CLOSE_ABS(0., sixthDensitySum1D, 1e-4);
0641 
0642   // Remove track 1 from 2D grid
0643   grid2D.subtractTrack(secondTrackDensityMap2D, mainDensityMap2D);
0644   // Size should not have changed
0645   BOOST_CHECK_EQUAL(trkGridSize + nNonOverlappingBins2D,
0646                     mainDensityMap2D.size());
0647   double sixthDensitySum2D = densitySum(mainDensityMap2D);
0648   // 2D grid is now empty after all tracks were removed
0649   CHECK_CLOSE_ABS(0., sixthDensitySum2D, 1e-4);
0650 }
0651 
0652 BOOST_AUTO_TEST_SUITE_END()
0653 
0654 }  // namespace ActsTests