File indexing completed on 2026-05-20 07:36:09
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/TrackParametrization.hpp"
0013 #include "Acts/Definitions/Units.hpp"
0014 #include "Acts/Surfaces/CurvilinearSurface.hpp"
0015 #include "Acts/Surfaces/PlaneSurface.hpp"
0016 #include "Acts/TrackFitting/GsfMixtureReduction.hpp"
0017 #include "Acts/TrackFitting/detail/GsfComponentMerging.hpp"
0018 #include "Acts/Utilities/Zip.hpp"
0019
0020 #include <algorithm>
0021 #include <cstddef>
0022 #include <memory>
0023 #include <numbers>
0024 #include <numeric>
0025 #include <random>
0026 #include <tuple>
0027 #include <vector>
0028
0029 using namespace Acts;
0030 using namespace Acts::UnitLiterals;
0031
0032 namespace {
0033
0034 std::tuple<BoundVector, double> computeMeanAndSumOfWeights(
0035 const std::vector<GsfComponent> &cmps, const Surface &surface) {
0036 if (cmps.empty()) {
0037 return {BoundVector::Zero(), 0.0};
0038 }
0039
0040 const double sumOfWeights = std::accumulate(
0041 cmps.begin(), cmps.end(), 0.0,
0042 [](auto sum, const auto &cmp) { return sum + cmp.weight; });
0043
0044 BoundVector mean;
0045 detail::Gsf::angleDescriptionSwitch(surface, [&](const auto &desc) {
0046 auto [meanTmp, cov] = detail::Gsf::mergeGaussianMixtureMeanCov(
0047 cmps,
0048 [](const auto &c) {
0049 return std::tie(c.weight, c.boundPars, c.boundCov);
0050 },
0051 desc);
0052 mean = meanTmp;
0053 });
0054
0055 return std::make_tuple(mean, sumOfWeights);
0056 }
0057
0058 GsfComponent makeDefaultComponent(double weight) {
0059 GsfComponent cmp;
0060 cmp.boundPars = BoundVector::Zero();
0061 cmp.boundCov = BoundMatrix::Identity();
0062 cmp.weight = weight;
0063 return cmp;
0064 }
0065
0066 void testReductionEquivalence(std::vector<GsfComponent> &cmpsRef,
0067 std::vector<GsfComponent> &cmpsTest) {
0068 BOOST_REQUIRE(cmpsRef.size() == cmpsTest.size());
0069
0070
0071
0072 std::ranges::sort(cmpsRef, {}, [](const auto &c) { return c.weight; });
0073 std::ranges::sort(cmpsTest, {}, [](const auto &c) { return c.weight; });
0074
0075 constexpr static double tol = 1.e-8;
0076
0077
0078 for (const auto &[ref, test] : Acts::zip(cmpsRef, cmpsTest)) {
0079 BOOST_CHECK_CLOSE(ref.weight, test.weight, tol);
0080 BOOST_CHECK_CLOSE(ref.boundPars[eBoundLoc0], test.boundPars[eBoundLoc0],
0081 tol);
0082 BOOST_CHECK_CLOSE(ref.boundPars[eBoundLoc1], test.boundPars[eBoundLoc1],
0083 tol);
0084 BOOST_CHECK_CLOSE(ref.boundPars[eBoundPhi], test.boundPars[eBoundPhi], tol);
0085 BOOST_CHECK_CLOSE(ref.boundPars[eBoundTheta], test.boundPars[eBoundTheta],
0086 tol);
0087 BOOST_CHECK_CLOSE(ref.boundPars[eBoundQOverP], test.boundPars[eBoundQOverP],
0088 tol);
0089 BOOST_CHECK_CLOSE(ref.boundPars[eBoundTime], test.boundPars[eBoundTime],
0090 tol);
0091 }
0092 }
0093
0094 }
0095
0096 namespace ActsTests {
0097
0098 BOOST_AUTO_TEST_SUITE(TrackFittingSuite)
0099
0100 BOOST_AUTO_TEST_CASE(test_distance_matrix_min_distance) {
0101 std::vector<GsfComponent> cmps = {
0102 {1. / 3., BoundVector::Constant(-2.), BoundMatrix::Identity()},
0103 {1. / 3., BoundVector::Constant(+0.), BoundMatrix::Identity()},
0104 {1. / 3., BoundVector::Constant(+1.), BoundMatrix::Identity()},
0105 {1. / 3., BoundVector::Constant(+4.), BoundMatrix::Identity()}};
0106
0107 detail::Gsf::SymmetricKLDistanceMatrix mat(cmps);
0108
0109 const auto [i, j] = mat.minDistancePair();
0110 BOOST_CHECK_EQUAL(std::min(i, j), 1);
0111 BOOST_CHECK_EQUAL(std::max(i, j), 2);
0112 }
0113
0114 BOOST_AUTO_TEST_CASE(test_distance_matrix_masking) {
0115 std::vector<GsfComponent> cmps = {
0116 {1. / 3., BoundVector::Constant(-2.), BoundMatrix::Identity()},
0117 {1. / 3., BoundVector::Constant(+0.), BoundMatrix::Identity()},
0118 {1. / 3., BoundVector::Constant(+1.), BoundMatrix::Identity()},
0119 {1. / 3., BoundVector::Constant(+4.), BoundMatrix::Identity()}};
0120
0121 const std::size_t cmp_to_mask = 2;
0122
0123 detail::Gsf::SymmetricKLDistanceMatrix mat_full(cmps);
0124 mat_full.maskAssociatedDistances(cmp_to_mask);
0125
0126 cmps.erase(cmps.begin() + cmp_to_mask);
0127 detail::Gsf::SymmetricKLDistanceMatrix mat_small(cmps);
0128
0129 const auto [full_i, full_j] = mat_full.minDistancePair();
0130 const auto [small_i, small_j] = mat_small.minDistancePair();
0131
0132 BOOST_CHECK_EQUAL(std::min(full_i, full_j), 0);
0133 BOOST_CHECK_EQUAL(std::max(full_i, full_j), 1);
0134 BOOST_CHECK_EQUAL(full_i, small_i);
0135 BOOST_CHECK_EQUAL(full_j, small_j);
0136 }
0137
0138 BOOST_AUTO_TEST_CASE(test_distance_matrix_recompute_distance) {
0139 std::vector<GsfComponent> cmps = {
0140 {1. / 3., BoundVector::Constant(-2.), BoundMatrix::Identity()},
0141 {1. / 3., BoundVector::Constant(+0.), BoundMatrix::Identity()},
0142 {1. / 3., BoundVector::Constant(+1.), BoundMatrix::Identity()},
0143 {1. / 3., BoundVector::Constant(+4.), BoundMatrix::Identity()}};
0144
0145 detail::Gsf::SymmetricKLDistanceMatrix mat(cmps);
0146
0147 {
0148 const auto [i, j] = mat.minDistancePair();
0149 BOOST_CHECK_EQUAL(std::min(i, j), 1);
0150 BOOST_CHECK_EQUAL(std::max(i, j), 2);
0151 }
0152
0153 cmps[3].boundPars = BoundVector::Constant(0.1);
0154 mat.recomputeAssociatedDistances(3, cmps);
0155
0156 {
0157 const auto [i, j] = mat.minDistancePair();
0158 BOOST_CHECK_EQUAL(std::min(i, j), 1);
0159 BOOST_CHECK_EQUAL(std::max(i, j), 3);
0160 }
0161
0162 cmps[0].boundPars = BoundVector::Constant(1.01);
0163 mat.recomputeAssociatedDistances(0, cmps);
0164
0165 {
0166 const auto [i, j] = mat.minDistancePair();
0167 BOOST_CHECK_EQUAL(std::min(i, j), 0);
0168 BOOST_CHECK_EQUAL(std::max(i, j), 2);
0169 }
0170 }
0171
0172 BOOST_AUTO_TEST_CASE(test_mixture_reduction) {
0173
0174 std::shared_ptr<PlaneSurface> surface =
0175 CurvilinearSurface(Vector3{0, 0, 0}, Vector3{1, 0, 0}).planeSurface();
0176 const std::size_t NComps = 4;
0177 std::vector<GsfComponent> cmps;
0178
0179 for (auto i = 0ul; i < NComps; ++i) {
0180 GsfComponent a;
0181 a.boundPars = BoundVector::Zero();
0182 a.boundCov = BoundMatrix::Identity();
0183 a.weight = 1.0 / NComps;
0184 cmps.push_back(a);
0185 }
0186
0187 cmps[0].boundPars[eBoundQOverP] = 0.5_GeV;
0188 cmps[1].boundPars[eBoundQOverP] = 1.5_GeV;
0189 cmps[2].boundPars[eBoundQOverP] = 3.5_GeV;
0190 cmps[3].boundPars[eBoundQOverP] = 4.5_GeV;
0191
0192
0193 const auto [mean0, sumOfWeights0] =
0194 computeMeanAndSumOfWeights(cmps, *surface);
0195
0196 BOOST_CHECK_CLOSE(mean0[eBoundQOverP], 2.5_GeV, 1.e-8);
0197 BOOST_CHECK_CLOSE(sumOfWeights0, 1.0, 1.e-8);
0198
0199
0200 reduceMixtureWithKLDistance(cmps, 2, *surface);
0201
0202 BOOST_CHECK_EQUAL(cmps.size(), 2);
0203
0204 std::ranges::sort(cmps, {},
0205 [](const auto &c) { return c.boundPars[eBoundQOverP]; });
0206 BOOST_CHECK_CLOSE(cmps[0].boundPars[eBoundQOverP], 1.0_GeV, 1.e-8);
0207 BOOST_CHECK_CLOSE(cmps[1].boundPars[eBoundQOverP], 4.0_GeV, 1.e-8);
0208
0209 const auto [mean1, sumOfWeights1] =
0210 computeMeanAndSumOfWeights(cmps, *surface);
0211
0212 BOOST_CHECK_CLOSE(mean1[eBoundQOverP], 2.5_GeV, 1.e-8);
0213 BOOST_CHECK_CLOSE(sumOfWeights1, 1.0, 1.e-8);
0214
0215
0216 reduceMixtureWithKLDistance(cmps, 1, *surface);
0217
0218 BOOST_CHECK_EQUAL(cmps.size(), 1);
0219 BOOST_CHECK_CLOSE(cmps[0].boundPars[eBoundQOverP], 2.5_GeV, 1.e-8);
0220 BOOST_CHECK_CLOSE(cmps[0].weight, 1.0, 1.e-8);
0221 }
0222
0223 BOOST_AUTO_TEST_CASE(test_weight_cut_reduction) {
0224 std::shared_ptr<PlaneSurface> dummy =
0225 CurvilinearSurface(Vector3{0, 0, 0}, Vector3{1, 0, 0}).planeSurface();
0226 std::vector<GsfComponent> cmps;
0227
0228
0229 for (auto w : {1.0, 2.0, 3.0, 4.0}) {
0230 GsfComponent a;
0231 a.boundPars = BoundVector::Zero();
0232 a.boundCov = BoundMatrix::Identity();
0233 a.weight = w;
0234 cmps.push_back(a);
0235 }
0236
0237 reduceMixtureLargestWeights(cmps, 2, *dummy);
0238
0239 BOOST_CHECK_EQUAL(cmps.size(), 2);
0240 std::ranges::sort(cmps, {}, [](const auto &c) { return c.weight; });
0241
0242 BOOST_CHECK_EQUAL(cmps[0].weight, 3.0);
0243 BOOST_CHECK_EQUAL(cmps[1].weight, 4.0);
0244 }
0245
0246 BOOST_AUTO_TEST_CASE(test_naive_vs_optimized) {
0247 std::shared_ptr<PlaneSurface> surface =
0248 CurvilinearSurface(Vector3{0, 0, 0}, Vector3{1, 0, 0}).planeSurface();
0249 const std::size_t NComps = 10;
0250
0251 std::mt19937 rng(42);
0252 std::uniform_real_distribution<double> weightDist(0.5, 1.5);
0253 std::uniform_real_distribution<double> loc0Dist(-10.0, 10.0);
0254 std::uniform_real_distribution<double> loc1Dist(-10.0, 10.0);
0255 std::uniform_real_distribution<double> phiDist(-std::numbers::pi,
0256 std::numbers::pi);
0257 std::uniform_real_distribution<double> thetaDist(0.0, std::numbers::pi);
0258 std::uniform_real_distribution<double> qopDist(0.1, 5.0);
0259
0260 std::vector<GsfComponent> cmps;
0261 double weightSum = 0.0;
0262
0263 for (auto i = 0ul; i < NComps; ++i) {
0264 GsfComponent cmp = makeDefaultComponent(weightDist(rng));
0265 cmp.boundPars[eBoundLoc0] = loc0Dist(rng);
0266 cmp.boundPars[eBoundLoc1] = loc1Dist(rng);
0267 cmp.boundPars[eBoundPhi] = phiDist(rng);
0268 cmp.boundPars[eBoundTheta] = thetaDist(rng);
0269 cmp.boundPars[eBoundQOverP] = qopDist(rng);
0270 cmp.boundPars[eBoundTime] = 0.0;
0271 weightSum += cmp.weight;
0272 cmps.push_back(cmp);
0273 }
0274
0275 for (auto &cmp : cmps) {
0276 cmp.weight /= weightSum;
0277 }
0278
0279 for (std::size_t targetSize = 9; targetSize > 0; --targetSize) {
0280 std::vector<GsfComponent> cmpsOptimized = cmps;
0281 std::vector<GsfComponent> cmpsNaive = cmps;
0282
0283 reduceMixtureWithKLDistance(cmpsOptimized, targetSize, *surface);
0284 reduceMixtureWithKLDistanceNaive(cmpsNaive, targetSize, *surface);
0285 BOOST_CHECK_EQUAL(cmpsNaive.size(), targetSize);
0286
0287 testReductionEquivalence(cmpsNaive, cmpsOptimized);
0288 }
0289 }
0290
0291 BOOST_AUTO_TEST_SUITE_END()
0292
0293 }