Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-10 08:00:12

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 #pragma once
0010 
0011 #include <algorithm>
0012 #include <array>
0013 #include <cmath>
0014 #include <concepts>
0015 #include <cstdint>
0016 #include <functional>
0017 #include <vector>
0018 
0019 namespace Acts::Experimental {
0020 
0021 /// @brief helper class for adaptive traversal of HT space
0022 class HoughAccumulatorSection {
0023  public:
0024   /// @brief Defines the fate of section during traversal
0025   enum class Decision {
0026     Discard,  //< the section is not to be explored further
0027     Accept,   //< the section should be accepted as solution without further
0028               //< exploration
0029     Drill,    //< the section should be expred further by splitting according to
0030             //< binning definition (split into 4 or 2 left-right or top-bottom)
0031     DrillAndExpand,  //< the section should be source of subsections as in the
0032                      //< case of drill & but the sections will be made larger
0033     //< size increase is configured in opt by relative factors @see expandX, @see expandY
0034   };
0035 
0036   HoughAccumulatorSection() = default;
0037 
0038   /// @brief Construct the section
0039   /// @param xw - width in x direction
0040   /// @param yw - widths in y direction
0041   /// @param xBegin - location of left side of the section
0042   /// @param yBegin - location of bottom side of the section
0043   /// @param div - division level (will be incremented during each division)
0044   /// @param indices - indices of measurements that generate lines passing this section
0045   /// @param history - storage for arbitrary information related to the section
0046   HoughAccumulatorSection(float xw, float yw, float xBegin, float yBegin,
0047                           int div = 0,
0048                           const std::vector<std::uint32_t> &indices = {},
0049                           const std::vector<float> &history = {});
0050 
0051   /// @brief decision designated for this section in next traversal step
0052   /// @return currently set decision
0053   Decision decision() const { return m_decision; }
0054 
0055   /// @brief updates decision designated for this step
0056   /// If this is not set specifically the default is to split (called Drill)
0057   /// @param d - new decision for the section
0058   /// @return value of the decision set
0059   Decision updateDecision(Decision d) { return m_decision = d; }
0060 
0061   /// @brief keep indices and update parameters of the box
0062   /// This method is useful when changing direction of the search
0063   /// @param xw - new x width
0064   /// @param yw - new x width
0065   /// @param xBegin - new left side
0066   /// @param yBegin - new bottom side
0067   void updateDimensions(float xw, float yw, float xBegin, float yBegin);
0068 
0069   /// @brief keep indices and update parameters of the box by scalling
0070   /// @param xs - scale in x direction, if bigger than 1 the size increases
0071   /// @param ys - scale in y direction
0072   /// The box is recentred
0073   void expand(float xs, float ys);
0074 
0075   /// @brief indices of measurements that result in lines in this section
0076   /// @return reference to the vector of indices
0077   inline const std::vector<std::uint32_t> &indices() const { return m_indices; }
0078 
0079   /// @brief mutable version of @see indices
0080   /// @return mutable access to indices
0081   inline std::vector<std::uint32_t> &indices() { return m_indices; }
0082 
0083   /// @brief number of lines in the section
0084   /// @return number of lines
0085   std::size_t count() const { return m_indices.size(); }
0086 
0087   /// @brief create bottom section that is result of splitting this one into 2
0088   /// @param copyIndices - copies indices from the parent if true
0089   /// +------+
0090   /// |      |
0091   /// +------+
0092   /// |     <|-- this part
0093   /// +------+
0094   /// @return - new section
0095   HoughAccumulatorSection bottom(bool copyIndices = false) const;
0096 
0097   /// @brief create top section that is result of splitting this one into 2
0098   /// @param copyIndices - copies indices from the parent if true
0099   /// +------+
0100   /// |      <|-- this part
0101   /// +------+
0102   /// |      |
0103   /// +------+
0104   /// @return - new section
0105   HoughAccumulatorSection top(bool copyIndices = false) const;
0106 
0107   /// @brief create left section that is result of splitting this one into 2
0108   /// @param copyIndices - copies indices from the parent if true
0109   /// +---+---+
0110   /// |  <|---|-- this part
0111   /// +---+---+
0112   /// @return - new section
0113   HoughAccumulatorSection left(bool copyIndices = false) const;
0114 
0115   /// @brief create right section that is result of splitting this one into 2
0116   /// @param copyIndices - copies indices from the parent if true
0117   /// +---+---+
0118   /// |   |  <|-- this part
0119   /// +---+---+
0120   /// @return - new section
0121   HoughAccumulatorSection right(bool copyIndices = false) const;
0122 
0123   /// @brief create section that is result of splitting this one into 4
0124   /// @arg copyIndices - copies indices from the parent
0125   /// create section that is bottom left corner of this this one
0126   /// by default the section is divided into 4 quadrants,
0127   /// if parameters are provided the quadrants size can be adjusted
0128   /// +---+---+
0129   /// |   |   |
0130   /// +---+---+
0131   /// |   |  <|-- this part
0132   /// +---+---+
0133   /// @return new accumulator section (with new dimensions and location)
0134   /// @param copyIndices - copies indices from the parent if true
0135   HoughAccumulatorSection bottomRight(bool copyIndices = false) const;
0136 
0137   /// @brief create section that is result of splitting this one into 4
0138   /// @param copyIndices - copies indices from the parent
0139   /// @see bottomRight
0140   /// @return new accumulator section (with new dimensions and location)
0141   HoughAccumulatorSection bottomLeft(bool copyIndices = false) const;
0142 
0143   /// @brief create section that is result of splitting this one into 4
0144   /// @param copyIndices - copies indices from the parent
0145   /// @see bottomRight
0146   /// @return new accumulator section (with new dimensions and location)
0147   HoughAccumulatorSection topLeft(bool copyIndices = false) const;
0148 
0149   /// @brief create section that is result of splitting this one into 4
0150   /// @param copyIndices - copies indices from the parent
0151   /// @see bottomRight
0152   /// @return new accumulator section (with new dimensions and location)
0153   HoughAccumulatorSection topRight(bool copyIndices = false) const;
0154 
0155   /// @brief true if the line defined by given parameters passes the section
0156   /// @param function is callable used to check crossing at the edges
0157   /// @return true if the line passes the section
0158   template <typename F>
0159   bool isLineInside(F &&function) const &
0160     requires std::invocable<F, float>;
0161 
0162   /// @brief check if the lines cross inside the section
0163   /// @param line1 - functional form of line 1
0164   /// @param line2 - functional form of line 2
0165   /// @warning note that this function is assuming that these are lines and the derivative is positive.
0166   /// @return true if the two lines cross in the section
0167   template <typename F>
0168   bool isCrossingInside(F &&line1, F &&line2) const &
0169     requires std::invocable<F, float>;
0170 
0171   /// @brief size accessor
0172   /// @return size in x direction
0173   float xSize() const { return m_xSize; }
0174   /// @brief size accessor
0175   /// @return size in x direction
0176   float ySize() const { return m_ySize; }
0177   /// @brief location accessor
0178   /// @return left side position
0179   float xBegin() const { return m_xBegin; }
0180   /// @brief location accessor
0181   /// @return bottom side position
0182   float yBegin() const { return m_yBegin; }
0183   /// @brief number of divisions that lead to this section
0184   /// @return integer > 0
0185   std::uint32_t divisionLevel() const { return m_divisionLevel; }
0186 
0187   /// store additional (arbitrary) info in indexed array
0188   /// @param index - identifier
0189   /// @param value - value to store
0190   void setHistory(std::size_t index, float value) {
0191     m_history.resize(std::max(index + 1, m_history.size()));
0192     m_history.at(index) = value;
0193   }
0194   /// @brief retrieve history info
0195   /// @param index - item index
0196   /// @return value stored by @see setHistory
0197   float history(std::uint32_t index) const { return m_history.at(index); }
0198 
0199  private:
0200   Decision m_decision = Decision::Drill;
0201   float m_xSize = 0;
0202   float m_ySize = 0;
0203   float m_xBegin = 0;
0204   float m_yBegin = 0;
0205   /// number of times the starting section was already divided
0206   std::uint32_t m_divisionLevel = 0;
0207   /// indices of measurements contributing to this section
0208   std::vector<std::uint32_t> m_indices;
0209   /// additional record where an arbitrary information can be stored
0210   std::vector<float> m_history;
0211 };
0212 
0213 template <typename F>
0214 inline bool HoughAccumulatorSection::isLineInside(F &&function) const &
0215   requires std::invocable<F, float>
0216 {
0217   const float yB = function(m_xBegin);
0218   const float yE = function(m_xBegin + m_xSize);
0219   return (yE > yB) ? yB < m_yBegin + m_ySize && yE > m_yBegin
0220                    : yB > m_yBegin && yE < m_yBegin + m_ySize;
0221 }
0222 
0223 template <typename F>
0224 inline bool HoughAccumulatorSection::isCrossingInside(F &&line1,
0225                                                       F &&line2) const &
0226   requires std::invocable<F, float>
0227 {
0228   // this microalgorithm idea is illustrated below
0229   // section left section right
0230   // example with crossing
0231   //                                       |            +2
0232   // line 1 crossing left section edge     +1          _|
0233   // left edge mid point                   |_           |
0234   //                                       |            +1
0235   // line 2crossing left section           +2           |
0236   //
0237   // example with no crossing
0238   //                                       |            +1
0239   // line 1 crossing left section edge     +1          _|
0240   // left edge mid point                   |_           |
0241   //                                       |            +2
0242   // line 2crossing left section           +2           |
0243   // The above covers most of the cases.
0244   // Additional precautions are made when both lines cross
0245   // left & right (x) bounds outside of vertical (y) bounds.
0246 
0247   const float xL = xBegin();
0248   const float xR = xBegin() + xSize();
0249 
0250   // Evaluate both lines at section boundaries
0251   const float y1L = line1(xL);
0252   const float y1R = line1(xR);
0253   const float y2L = line2(xL);
0254   const float y2R = line2(xR);
0255 
0256   // --- Step 1: Quick rejection based on ordering ---
0257   // If one line is consistently above the other → no crossing
0258   const float dL = y1L - y2L;
0259   const float dR = y1R - y2R;
0260 
0261   if (dL * dR > 0) {
0262     return false;
0263   }
0264 
0265   // --- Step 2: Check if either line fully spans inside vertically ---
0266   const auto checkVerticalOverlap = [this](float y) {
0267     return yBegin() < y && y < yBegin() + ySize();
0268   };
0269 
0270   if (checkVerticalOverlap(y1L) && checkVerticalOverlap(y1R)) {
0271     return true;
0272   }
0273 
0274   if (checkVerticalOverlap(y2L) && checkVerticalOverlap(y2R)) {
0275     return true;
0276   }
0277 
0278   // --- Step 3: Approximate crossing position ---
0279   // Linear interpolation assuming near-linear behavior
0280   const float abs_dL = std::abs(dL);
0281   const float abs_dR = std::abs(dR);
0282 
0283   // Avoid division by zero (parallel & overlapping edge case)
0284   if (abs_dL + abs_dR == 0.0f) {
0285     return false;
0286   }
0287 
0288   const float t = abs_dL / (abs_dL + abs_dR);  // fraction from left
0289   const float xCross = std::lerp(xL, xR, t);
0290 
0291   // --- Step 4: Check if crossing lies inside vertical bounds ---
0292   const float yCross1 = line1(xCross);
0293   const float yCross2 = line2(xCross);
0294   const float yCross = 0.5f * (yCross1 + yCross2);  // intersection approx
0295 
0296   return checkVerticalOverlap(yCross);
0297 }
0298 
0299 /// @brief structure that keeps HT space traversal options
0300 /// @tparam Measurement - measurement type which are used in generating lines in HT space
0301 template <typename Measurement>
0302 struct HoughExplorationOptions {
0303   /// minimum bin size in x direction, beyond that
0304   /// value the sections are not split
0305   float xMinBinSize = 1.0f;
0306 
0307   /// minimum bin size in y direction, beyond that
0308   /// value the sections are not split
0309   float yMinBinSize = 1.0f;
0310 
0311   /// expand in x direction (default by 10%) if Expand
0312   /// Decision is made
0313   float expandX = 1.1f;
0314 
0315   /// expand in y direction (default by 10%) if Expand
0316   /// Decision is made
0317   float expandY = 1.1f;
0318 
0319   /// @brief functional, that given measurement and argument in HT space x return y
0320   using LineFunctor = std::function<float(const Measurement &, float)>;
0321 
0322   /// functional that, given measurement and
0323   /// "x" coordinate of Hough space return "y" coordinate
0324   LineFunctor lineFunctor;
0325   /// @brief functional type, that given section and measurements decides the evolution of section
0326   using DecisionFunctor = std::function<HoughAccumulatorSection::Decision(
0327       const HoughAccumulatorSection &, const std::vector<Measurement> &)>;
0328 
0329   /// function deciding if the accumulator section should be, discarded,
0330   /// split further (and how), or is a solution
0331   DecisionFunctor decisionFunctor;
0332 };
0333 
0334 /// @brief function that walks through the section splitting them (depth-first) and
0335 /// @tparam Measurement - type of measurements
0336 /// @tparam Functor - for translation from measurements to Hough space
0337 /// @param sectionsStack - the stacks to consider
0338 /// @param measurements - measurements to which indices are kept in HoughAccumulatorSection
0339 /// @param opt - exploration directives
0340 /// @param results - sectoins that satisfied acceptance criteria
0341 template <typename Measurement>
0342 void exploreHoughParametersSpace(
0343     std::vector<HoughAccumulatorSection> &sectionsStack,
0344     const std::vector<Measurement> &measurements,
0345     const HoughExplorationOptions<Measurement> &opt,
0346     std::vector<HoughAccumulatorSection> &results) {
0347   while (!sectionsStack.empty()) {
0348     HoughAccumulatorSection thisSection = std::move(sectionsStack.back());
0349     sectionsStack.pop_back();
0350 
0351     std::vector<HoughAccumulatorSection> newSections;
0352     const bool splitX = thisSection.xSize() > opt.xMinBinSize;
0353     const bool splitY = thisSection.ySize() > opt.yMinBinSize;
0354     if (splitX && splitY) {
0355       // Split into 4 sections
0356       newSections.push_back(thisSection.bottomLeft());
0357       newSections.push_back(thisSection.topLeft());
0358       newSections.push_back(thisSection.bottomRight());
0359       newSections.push_back(thisSection.topRight());
0360     } else if (!splitX && splitY) {
0361       // Split into 2 sections horizontally
0362       newSections.push_back(thisSection.bottom());
0363       newSections.push_back(thisSection.top());
0364     } else if (splitX && !splitY) {
0365       // Split into 2 sections vertically
0366       newSections.push_back(thisSection.left());
0367       newSections.push_back(thisSection.right());
0368     }
0369 
0370     if (thisSection.decision() ==
0371         HoughAccumulatorSection::Decision::DrillAndExpand) {
0372       for (HoughAccumulatorSection &s : newSections) {
0373         s.expand(opt.expandX, opt.expandY);
0374       }
0375     }
0376 
0377     for (const std::uint32_t idx : thisSection.indices()) {
0378       const auto &m = measurements[idx];
0379       const auto line = [&](float x) { return opt.lineFunctor(m, x); };
0380 
0381       for (HoughAccumulatorSection &s : newSections) {
0382         if (s.isLineInside(line)) {
0383           s.indices().push_back(idx);
0384         }
0385       }
0386     }
0387     for (HoughAccumulatorSection &s : newSections) {
0388       s.updateDecision(opt.decisionFunctor(s, measurements));
0389       if (s.decision() == HoughAccumulatorSection::Decision::Accept) {
0390         results.push_back(std::move(s));
0391       } else if (s.decision() == HoughAccumulatorSection::Decision::Drill ||
0392                  s.decision() ==
0393                      HoughAccumulatorSection::Decision::DrillAndExpand) {
0394         sectionsStack.push_back(std::move(s));
0395       }
0396     }
0397   }
0398 }
0399 
0400 /// @brief Helper for fast check if there is enough line crossings within HoughAccumulatorSection
0401 /// @tparam Measurement
0402 /// @tparam Functor
0403 /// @param section - section to rest against
0404 /// @param measurements - vector of measurements
0405 /// @param lineFunctor - the function defining line
0406 /// @param threshold - number of crossings required for positive decision (it is provided so early exit can be implemented)
0407 /// @return true if the check passes
0408 template <typename Measurement, typename Functor>
0409 bool passIntersectionsCheck(const HoughAccumulatorSection &section,
0410                             const std::vector<Measurement> &measurements,
0411                             const Functor &lineFunctor,
0412                             const std::uint32_t threshold) {
0413   const std::size_t count = section.count();
0414   const float xLeft = section.xBegin();
0415   const float xRight = xLeft + section.xSize();
0416   const auto &indices = section.indices();
0417 
0418   // Small Buffer Optimization
0419   constexpr std::size_t kMaxStackLines = 64;
0420 
0421   if (count <= kMaxStackLines) {
0422     std::array<float, kMaxStackLines> yLeft{};
0423     std::array<float, kMaxStackLines> yRight{};
0424 
0425     for (std::size_t i = 0; i < count; ++i) {
0426       const auto &m = measurements[indices[i]];
0427       yLeft[i] = lineFunctor(m, xLeft);
0428       yRight[i] = lineFunctor(m, xRight);
0429     }
0430 
0431     std::uint32_t inside = 0;
0432     for (std::uint32_t i = 0; i < count; ++i) {
0433       for (std::uint32_t j = i + 1; j < count; ++j) {
0434         if ((yLeft[i] - yLeft[j]) * (yRight[i] - yRight[j]) < 0.0f) {
0435           inside++;
0436           if (inside >= threshold) {
0437             return true;  // Early exit
0438           }
0439         }
0440       }
0441     }
0442     return false;
0443   } else {
0444     std::vector<float> yLeft(count);
0445     std::vector<float> yRight(count);
0446     for (std::uint32_t i = 0; i < count; ++i) {
0447       const auto &m = measurements[indices[i]];
0448       yLeft[i] = lineFunctor(m, xLeft);
0449       yRight[i] = lineFunctor(m, xRight);
0450     }
0451     std::uint32_t inside = 0;
0452     for (std::uint32_t i = 0; i < count; ++i) {
0453       for (std::uint32_t j = i + 1; j < count; ++j) {
0454         if ((yLeft[i] - yLeft[j]) * (yRight[i] - yRight[j]) < 0.0f) {
0455           inside++;
0456           if (inside >= threshold) {
0457             return true;  // Early exit
0458           }
0459         }
0460       }
0461     }
0462     return inside >= threshold;
0463   }
0464 }
0465 
0466 }  // namespace Acts::Experimental