Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-10 08:02:34

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/Seeding2/HoughAccumulatorSection.hpp"
0012 #include "Acts/Utilities/Logger.hpp"
0013 
0014 #include <functional>
0015 #include <map>
0016 #include <vector>
0017 
0018 using namespace Acts;
0019 using namespace Acts::Experimental;
0020 
0021 namespace ActsTests {
0022 
0023 auto logger = getDefaultLogger("UnitTests", Logging::VERBOSE);
0024 
0025 /// @brief Structure representing test parameters for a line in the accumulator space: y
0026 /// = slope * x + intercept
0027 struct LineParameters {
0028   float slope;
0029   float intercept;
0030 };
0031 
0032 // Structure for statistics to enable BOOST_CHECK on results
0033 struct Stats {
0034   double area{};
0035   std::int32_t nSections{};
0036   std::int32_t nLines{};
0037   std::int32_t discardedByThresholdCut{};
0038   std::int32_t discardedByCrossingCut{};
0039 };
0040 
0041 template <typename measurement_t = LineParameters>
0042 struct TestExplorationOptions : public HoughExplorationOptions<LineParameters> {
0043   std::uint32_t threshold =
0044       4;  // number of lines passing section for it to be still considered
0045   std::uint32_t noiseThreshold = 12;  // number of lines passing section at the
0046                                       // final split to consider it noise
0047   std::uint32_t min_division_level = 2;
0048 };
0049 
0050 // SUITE 1: HoughAccumulatorSection Test
0051 BOOST_AUTO_TEST_SUITE(HoughAccumulatorSectionSuite)
0052 
0053 using namespace ActsTests;
0054 
0055 BOOST_AUTO_TEST_CASE(construct) {
0056   HoughAccumulatorSection s(10., 100., -5., -50.0);
0057   BOOST_CHECK_EQUAL(s.xSize(), 10.);
0058   BOOST_CHECK_EQUAL(s.ySize(), 100.);
0059   BOOST_CHECK_EQUAL(s.xBegin(), -5.);
0060   BOOST_CHECK_EQUAL(s.yBegin(), -50.);
0061 }
0062 
0063 BOOST_AUTO_TEST_CASE(split_vertical) {
0064   HoughAccumulatorSection s(10., 100., -5., -50.0);
0065   HoughAccumulatorSection t = s.top();
0066   HoughAccumulatorSection b = s.bottom();
0067   BOOST_CHECK_EQUAL(s.xSize(), t.xSize());
0068   BOOST_CHECK_EQUAL(s.xSize(), b.xSize());
0069   BOOST_CHECK_EQUAL(0.5f * s.ySize(), t.ySize());
0070   BOOST_CHECK_EQUAL(0.5f * s.ySize(), b.ySize());
0071   BOOST_CHECK_EQUAL(s.xBegin(), t.xBegin());
0072   BOOST_CHECK_EQUAL(s.xBegin(), b.xBegin());
0073   BOOST_CHECK_EQUAL(t.yBegin(), 0.0);
0074   BOOST_CHECK_EQUAL(b.yBegin(), -50.0);
0075 }
0076 
0077 BOOST_AUTO_TEST_CASE(split_horizontal) {
0078   HoughAccumulatorSection s(10., 100., -5., -50.0);
0079   HoughAccumulatorSection l = s.left();
0080   HoughAccumulatorSection r = s.right();
0081   BOOST_CHECK_EQUAL(s.ySize(), l.ySize());
0082   BOOST_CHECK_EQUAL(s.ySize(), r.ySize());
0083   BOOST_CHECK_EQUAL(0.5f * s.xSize(), l.xSize());
0084   BOOST_CHECK_EQUAL(0.5f * s.xSize(), r.xSize());
0085   BOOST_CHECK_EQUAL(l.xBegin(), -5.0);
0086   BOOST_CHECK_EQUAL(r.xBegin(), 0.0);
0087 }
0088 
0089 BOOST_AUTO_TEST_CASE(split_4) {
0090   HoughAccumulatorSection s(10., 100., 5., 50.0);
0091   HoughAccumulatorSection tl = s.topLeft();
0092   HoughAccumulatorSection tr = s.topRight();
0093   HoughAccumulatorSection bl = s.bottomLeft();
0094   HoughAccumulatorSection br = s.bottomRight();
0095 
0096   BOOST_CHECK_EQUAL(tl.xBegin(), 5.0);
0097   BOOST_CHECK_EQUAL(tl.yBegin(), 100.0);
0098   BOOST_CHECK_EQUAL(tl.xSize(), 5.0);
0099   BOOST_CHECK_EQUAL(tl.ySize(), 50.0);
0100 
0101   BOOST_CHECK_EQUAL(tr.xBegin(), 10.0);
0102   BOOST_CHECK_EQUAL(tr.yBegin(), 100.0);
0103   BOOST_CHECK_EQUAL(tr.xSize(), 5.0);
0104   BOOST_CHECK_EQUAL(tr.ySize(), 50.0);
0105 
0106   BOOST_CHECK_EQUAL(bl.xBegin(), 5.0);
0107   BOOST_CHECK_EQUAL(bl.yBegin(), 50.0);
0108   BOOST_CHECK_EQUAL(bl.xSize(), 5.0);
0109   BOOST_CHECK_EQUAL(bl.ySize(), 50.0);
0110 
0111   BOOST_CHECK_EQUAL(br.xBegin(), 10.0);
0112   BOOST_CHECK_EQUAL(br.yBegin(), 50.0);
0113   BOOST_CHECK_EQUAL(br.xSize(), 5.0);
0114   BOOST_CHECK_EQUAL(br.ySize(), 50.0);
0115 }
0116 
0117 BOOST_AUTO_TEST_CASE(is_line_inside) {
0118   HoughAccumulatorSection s(10., 2., -5., -1.0);
0119   BOOST_CHECK(!s.isLineInside([](float x) { return 0.5f * x + 5.f; }));
0120   BOOST_CHECK(s.isLineInside([](float x) { return 0.5f * x + 3.f; }));
0121   BOOST_CHECK(s.isLineInside([](float x) { return 0.1f * x; }));
0122   BOOST_CHECK(s.isLineInside([](float x) { return 1.0f * x - 5.f; }));
0123   BOOST_CHECK(!s.isLineInside([](float x) { return 0.5f * x - 5.f; }));
0124 }
0125 
0126 BOOST_AUTO_TEST_CASE(is_crossing_inside) {
0127   HoughAccumulatorSection s(10., 6., -5., -6.);
0128   std::function<float(float)> l1 = [](float x) { return 1.0f * x + 3.f; };
0129   std::function<float(float)> l2 = [](float x) { return 0.5f * x + 1.f; };
0130   std::function<float(float)> l3 = [](float x) { return 0.5f * x - 1.f; };
0131   std::function<float(float)> l4 = [](float x) { return 1.0f * x - 3.f; };
0132 
0133   BOOST_CHECK(s.isCrossingInside(l1, l2));
0134   BOOST_CHECK(!s.isCrossingInside(l2, l3));
0135   BOOST_CHECK(!s.isCrossingInside(l1, l3));
0136   BOOST_CHECK(!s.isCrossingInside(l4, l3));
0137 }
0138 
0139 BOOST_AUTO_TEST_CASE(history_handing) {
0140   HoughAccumulatorSection s;
0141   s.setHistory(2, 0.6f);  // 2 is index
0142   s.setHistory(0, 1.0f);  // 0 is index
0143 
0144   BOOST_CHECK_EQUAL(s.history(0), 1.0f);
0145   BOOST_CHECK_EQUAL(s.history(2), 0.6f);
0146 }
0147 
0148 BOOST_AUTO_TEST_SUITE_END()
0149 
0150 // SUITE 2: exploreParametersSpace Test
0151 BOOST_AUTO_TEST_SUITE(ExploreParametersSpaceSuite)
0152 
0153 BOOST_AUTO_TEST_CASE(test_extra_split_x_min_div_0) {
0154   // DESCRIPTION:
0155   // This test verifies the asymmetric binary split logic (!splitX && splitY /
0156   // splitX && !splitY) We define 3 lines with very low slopes that cross in the
0157   // center of the X-axis but remain entirely in the lower half of the Y-axis.
0158   // The grid has a yMinBinSize (10.0) and a smaller xMinBinSize (6.0)
0159 
0160   std::vector<LineParameters> measurements = {
0161       {0.05f, 1.0f},   // y = 0.05x + 1
0162       {0.08f, 1.01f},  // y = 0.08x + 1.01
0163       {-0.10f, 3.0f}   // y = -0.1x + 3
0164   };
0165 
0166   TestExplorationOptions<LineParameters> opt;
0167   opt.xMinBinSize = 6.0f;
0168   opt.yMinBinSize = 10.0f;
0169   opt.noiseThreshold = 3;
0170   opt.threshold = 2;
0171   opt.min_division_level = 0;
0172   // Linear function definition: y = slope * x + intercept
0173   opt.lineFunctor = [](const LineParameters &p, float arg) {
0174     return p.slope * arg + p.intercept;
0175   };
0176 
0177   HoughAccumulatorSection section(20.0f, 20.0f, 0.0f, 0.0f, 0);
0178   section.indices() = {0, 1, 2};
0179 
0180   std::map<int, Stats> sStat;
0181 
0182   opt.decisionFunctor = [&sStat, &opt](const HoughAccumulatorSection &sec,
0183                                        const std::vector<LineParameters> &mes) {
0184     using enum HoughAccumulatorSection::Decision;
0185 
0186     if (sec.count() < opt.threshold) {
0187       sStat[sec.divisionLevel()].discardedByThresholdCut += 1;
0188       return Discard;
0189     }
0190     if (sec.count() < 3 * opt.threshold &&
0191         !passIntersectionsCheck(sec, mes, opt.lineFunctor,
0192                                 opt.threshold * (opt.threshold - 1))) {
0193       sStat[sec.divisionLevel()].discardedByCrossingCut += 1;
0194       return Discard;
0195     }
0196 
0197     sStat[sec.divisionLevel()].area += sec.xSize() * sec.ySize();
0198     sStat[sec.divisionLevel()].nSections += 1;
0199     sStat[sec.divisionLevel()].nLines += sec.count();
0200 
0201     if (sec.divisionLevel() <= opt.min_division_level) {
0202       return Drill;
0203     }
0204     if (sec.count() <= opt.noiseThreshold && sec.xSize() <= opt.xMinBinSize &&
0205         sec.ySize() <= opt.yMinBinSize) {
0206       return Accept;
0207     }
0208 
0209     return Drill;
0210   };
0211 
0212   std::vector<HoughAccumulatorSection> sectionsStack;
0213   sectionsStack.push_back(std::move(section));
0214   std::vector<HoughAccumulatorSection> results;
0215 
0216   exploreHoughParametersSpace(sectionsStack, measurements, opt, results);
0217 
0218   BOOST_CHECK(sectionsStack.empty());
0219 
0220   BOOST_REQUIRE_EQUAL(results.size(), 1);
0221   BOOST_CHECK_EQUAL(results[0].xSize(), 5.0);
0222   BOOST_CHECK_EQUAL(results[0].ySize(), 10.0);
0223 
0224   // ==========================================
0225   // TREE EXECUTION TRACE
0226   // ==========================================
0227 
0228   // LEVEL 0:
0229   // this section is not tested with decisionFunctor and so the statistics is
0230   // not collected
0231 
0232   // LEVEL 1: Split into 4 (10x10). The lines are in the bottom half.
0233   // Section(10, 10, 0, 10) [tL] - Discarded by threshold cut (0 lines).
0234   // Section(10, 10, 10, 10) [tR] - Discarded by threshold cut (0 lines).
0235   // Section(10, 10, 0, 0) [bL] - Discarded by crossing cut (has 3 lines, but no
0236   // intersections inside). Section(10, 10, 10, 0) [bR] - Contains crossings ->
0237   // Drill.
0238   BOOST_CHECK_EQUAL(sStat[1].discardedByThresholdCut, 2);
0239   BOOST_CHECK_EQUAL(sStat[1].discardedByCrossingCut, 1);
0240   BOOST_CHECK_EQUAL(sStat[1].nSections, 1);
0241   BOOST_CHECK_EQUAL(sStat[1].area, 100.0);
0242 
0243   // LEVEL 2: bR section splits. ySize(10) is NO longer > yMinBinSize(10).
0244   // splitX=true, splitY=false. Splits vertically into 2 (5x10).
0245   // Section(5, 10, 15, 0) [right] - Discarded by crossing cut (intersections
0246   // are around x=13). Section(5, 10, 10, 0) [left] - Contains crossings.
0247   // xSize(5) <= xMinBinSize(6). -> Accept
0248   BOOST_CHECK_EQUAL(sStat[2].nSections, 1);
0249   BOOST_CHECK_EQUAL(sStat[2].area, 50.0);
0250   BOOST_CHECK_EQUAL(sStat[2].discardedByThresholdCut, 0);
0251   BOOST_CHECK_EQUAL(sStat[2].discardedByCrossingCut, 1);
0252 }
0253 
0254 BOOST_AUTO_TEST_CASE(test_with_min_div_lvl_is_1) {
0255   // DESCRIPTION:
0256   // This test verifies the standard QuadTree exploration (splitX && splitY).
0257   // We inject 5 direct line parameter sets (y = slope*x + intercept) into a
0258   // 10x20 area. min_division_level is set to 1, forcing an initial blind split
0259   // into 4 sections, after which the Early Culling and Small Buffer
0260   // Optimization (SBO) logic takes over.
0261 
0262   // Basic config
0263   std::vector<LineParameters> measurements = {
0264       {5.0, 3.0},   // Line 0
0265       {4.0, -4.0},  // Lina 1
0266       {1.0, 2.0},   // Line 2
0267       {0.5, 7.0},   // Line 3
0268       {0.2, 1.01}   // Line 4
0269   };
0270 
0271   TestExplorationOptions<LineParameters> opt;
0272   opt.xMinBinSize = 2.51;
0273   opt.yMinBinSize = 2.51;
0274   opt.noiseThreshold = 3;
0275   opt.threshold = 2;
0276   opt.min_division_level = 1;
0277   // Linear function definition: y = a*x + b, where LineParameters stores (a, b)
0278   // parameters for test purposes.
0279   opt.lineFunctor = [](const LineParameters &p, float arg) {
0280     return p.slope * arg + p.intercept;
0281   };
0282 
0283   // Create initial section and populate it with indices mapping to our 3
0284   // measurements
0285   HoughAccumulatorSection section(10.0, 20.0, -5.0, -10.0);
0286   section.indices() = {0, 1, 2, 3, 4};
0287 
0288   // Statistics map for verification in tests
0289   std::map<int, Stats> sStat;
0290 
0291   // Optimized decision functor
0292   opt.decisionFunctor = [&sStat, &opt](const HoughAccumulatorSection &sec,
0293                                        const std::vector<LineParameters> &mes) {
0294     using enum HoughAccumulatorSection::Decision;
0295     if (sec.count() < opt.threshold) {
0296       sStat[sec.divisionLevel()].discardedByThresholdCut += 1;
0297       return Discard;
0298     }
0299     if (sec.count() < 3 * opt.threshold &&
0300         !passIntersectionsCheck(sec, mes, opt.lineFunctor,
0301                                 opt.threshold * (opt.threshold - 1))) {
0302       sStat[sec.divisionLevel()].discardedByCrossingCut += 1;
0303       return Discard;
0304     }
0305     // Stats to save info about not discarded accumulator space
0306     sStat[sec.divisionLevel()].area += sec.xSize() * sec.ySize();
0307     sStat[sec.divisionLevel()].nSections += 1;
0308     sStat[sec.divisionLevel()].nLines += sec.count();
0309 
0310     if (sec.divisionLevel() <= opt.min_division_level) {
0311       return Drill;
0312     }
0313     if (sec.count() <= opt.noiseThreshold && sec.xSize() <= opt.xMinBinSize &&
0314         sec.ySize() <= opt.yMinBinSize) {
0315       std::cerr << "Accepted section: \n";
0316       return Accept;
0317     }
0318 
0319     return Drill;
0320   };
0321 
0322   std::vector<HoughAccumulatorSection> sectionsStack;
0323   sectionsStack.push_back(std::move(section));  // pushing root node
0324 
0325   std::vector<HoughAccumulatorSection> results;
0326 
0327   // Core engine
0328   exploreHoughParametersSpace(sectionsStack, measurements, opt, results);
0329 
0330   // Initial Assertions to verify the engine explored the space
0331   BOOST_CHECK(!sStat.empty());  // Verifies the tree was drilled
0332 
0333   // Empty section stack after algorithm
0334   BOOST_CHECK(sectionsStack.empty());
0335 
0336   // Results vector tests
0337   BOOST_REQUIRE_EQUAL(results.size(), 2);
0338   BOOST_CHECK_EQUAL(results[0].xSize(), 2.5);
0339   BOOST_CHECK_EQUAL(results[0].count(), 3);
0340   BOOST_CHECK_EQUAL(results[1].ySize(), 2.5);
0341   BOOST_CHECK_EQUAL(results[1].count(), 3);
0342 
0343   // ==========================================
0344   // TREE EXECUTION TRACE
0345   // ==========================================
0346 
0347   // LEVEL 0:
0348   // this section is not tested with decisionFunctor and so the statistics is
0349   // not collected
0350 
0351   // LEVEL 1: Splits into 4 (5.0 x 10.0).
0352   // 1 section discarded by threshold cut.
0353   // 1 section discarded by crossing cut.
0354   // 2 sections survive -> Drill.
0355   BOOST_CHECK_EQUAL(sStat[1].nSections, 2);
0356   BOOST_CHECK_EQUAL(sStat[1].area, 100.0);
0357   BOOST_CHECK_EQUAL(sStat[1].nLines, 9);
0358   BOOST_CHECK_EQUAL(sStat[1].discardedByThresholdCut, 1);
0359   BOOST_CHECK_EQUAL(sStat[1].discardedByCrossingCut, 1);
0360 
0361   // LEVEL 2: 2 sections split into 4 each (Total 8 generated sections of 2.5
0362   // x 5.0). 2 sections discarded by threshold cut. 4 sections discarded by
0363   // crossing cut. 2 sections survive -> Drill.
0364   BOOST_CHECK_EQUAL(sStat[2].nSections, 2);
0365   BOOST_CHECK_EQUAL(sStat[2].area, 25.0);
0366   BOOST_CHECK_EQUAL(sStat[2].nLines, 7);
0367   BOOST_CHECK_EQUAL(sStat[2].discardedByThresholdCut, 2);
0368   BOOST_CHECK_EQUAL(sStat[2].discardedByCrossingCut, 4);
0369 
0370   // LEVEL 3: 2 sections split. xSize(2.5) is NO longer > xMinBinSize(2.5).
0371   // splitX=false, splitY=true. Splits horizontally into 2 each (Total 4
0372   // sections of 2.5 x 2.5). 1 section discarded by threshold cut. 1 section
0373   // discarded by crossing cut. 2 sections survive. Size is (2.5 x 2.5) <=
0374   // minBins. -> Accept
0375   BOOST_CHECK_EQUAL(sStat[3].nSections, 2);
0376   BOOST_CHECK_EQUAL(sStat[3].area, 12.5);
0377   BOOST_CHECK_EQUAL(sStat[3].nLines, 6);
0378   BOOST_CHECK_EQUAL(sStat[3].discardedByThresholdCut, 1);
0379   BOOST_CHECK_EQUAL(sStat[3].discardedByCrossingCut, 1);
0380 }
0381 
0382 BOOST_AUTO_TEST_CASE(test_drill_and_expand_logic) {
0383   // DESCRIPTION:
0384   // This test verifies how new ExploreParametersSpace algorithm works with
0385   // DrillAndExpand option in the decision functor.
0386   // Bottom-right section is the found solution thanks to expanding sections
0387 
0388   std::vector<LineParameters> measurements = {
0389       {0.0f, -3.5f},  // y = -3.5
0390       {2.0f, -4.0f},  // y = 2x - 4
0391       {0.0f, 0.5f}    //  y = 0.5
0392   };
0393 
0394   TestExplorationOptions<LineParameters> opt;
0395   opt.xMinBinSize = 6.0f;
0396   opt.yMinBinSize = 6.0f;
0397   opt.noiseThreshold = 3;
0398   opt.threshold = 2;
0399   opt.min_division_level = 0;
0400   opt.expandX = 1.5;
0401   opt.expandY = 1.5;
0402   opt.lineFunctor = [](const LineParameters &p, float arg) {
0403     return p.slope * arg + p.intercept;
0404   };
0405 
0406   HoughAccumulatorSection section(8.0f, 8.0f, -4.0f, -4.0f, 0);
0407   section.updateDecision(HoughAccumulatorSection::Decision::DrillAndExpand);
0408   section.indices() = {0, 1, 2};
0409 
0410   std::map<int, Stats> sStat;
0411 
0412   opt.decisionFunctor = [&sStat, &opt](const HoughAccumulatorSection &sec,
0413                                        const std::vector<LineParameters> &mes) {
0414     using enum HoughAccumulatorSection::Decision;
0415     if (sec.count() < opt.threshold) {
0416       sStat[sec.divisionLevel()].discardedByThresholdCut += 1;
0417       return Discard;
0418     }
0419     if (sec.count() < 3 * opt.threshold &&
0420         !passIntersectionsCheck(sec, mes, opt.lineFunctor,
0421                                 opt.threshold * (opt.threshold - 1))) {
0422       sStat[sec.divisionLevel()].discardedByCrossingCut += 1;
0423       return Discard;
0424     }
0425 
0426     sStat[sec.divisionLevel()].area += sec.xSize() * sec.ySize();
0427     sStat[sec.divisionLevel()].nSections += 1;
0428     sStat[sec.divisionLevel()].nLines += sec.count();
0429 
0430     if (sec.divisionLevel() <= opt.min_division_level) {
0431       return DrillAndExpand;
0432     }
0433     if (sec.count() <= opt.noiseThreshold && sec.xSize() <= opt.xMinBinSize &&
0434         sec.ySize() <= opt.yMinBinSize) {
0435       return Accept;
0436     }
0437     return DrillAndExpand;
0438   };
0439 
0440   std::vector<HoughAccumulatorSection> sectionsStack;
0441   sectionsStack.push_back(std::move(section));
0442   std::vector<HoughAccumulatorSection> results;
0443 
0444   exploreHoughParametersSpace(sectionsStack, measurements, opt, results);
0445 
0446   BOOST_CHECK(sectionsStack.empty());
0447 
0448   BOOST_REQUIRE_EQUAL(results.size(), 1);
0449   BOOST_CHECK_EQUAL(results[0].xSize(), 6.0);
0450   BOOST_CHECK_EQUAL(results[0].ySize(), 6.0);
0451 
0452   // ==========================================
0453   // TREE EXECUTION TRACE
0454   // ==========================================
0455 
0456   // LEVEL 0:
0457   // this section is not tested with decisionFunctor and so the statistics is
0458   // not collected
0459 
0460   // LEVEL 1: Splits into 4 sections, but each expands by 1.5 in both directions
0461   // (4.0 -> 6.0). Section(6.0, 6.0, -5.0, 1.0) [tL] - Discarded by threshold
0462   // cut (only y=0.5 reaches it). Section(6.0, 6.0, -1.0, 1.0) [tR] - Discarded
0463   // by crossing cut (has 2 lines, but no crossings inside). Section(6.0, 6.0,
0464   // -5.0, -5.0) [bL] - Discarded by crossing cut (has enough lines but misses
0465   // intersection). Section(6.0, 6.0, -1.0, -5.0) [bR] - Intersection captured
0466   // inside thanks to expansion -> Accept
0467   BOOST_CHECK_EQUAL(sStat[1].discardedByThresholdCut, 1);
0468   BOOST_CHECK_EQUAL(sStat[1].discardedByCrossingCut, 2);
0469   BOOST_CHECK_EQUAL(sStat[1].nSections, 1);
0470   BOOST_CHECK_EQUAL(sStat[1].area, 36.0);
0471   BOOST_CHECK_EQUAL(sStat[1].nLines, 3);
0472 }
0473 
0474 BOOST_AUTO_TEST_SUITE_END()
0475 
0476 }  // namespace ActsTests