5959#include < stdexcept>
6060#include < unordered_set>
6161#include < vector>
62+ #include < chrono> // for std::chrono
6263
6364/* * Library version: 0xMmP (M=Major,m=minor,P=patch) */
6465#define NANOFLANN_VERSION 0x171
6566
67+ /* * Returns the current time in seconds since epoch */
68+ inline double getCurrentTime ()
69+ {
70+ auto now = std::chrono::high_resolution_clock::now ();
71+ auto duration = now.time_since_epoch ();
72+ return std::chrono::duration_cast<std::chrono::duration<double >>(duration).count ();
73+ }
74+
6675// Avoid conflicting declaration of min/max macros in Windows headers
6776#if !defined(NOMINMAX) && \
6877 (defined (_WIN32) || defined (_WIN32_) || defined (WIN32) || defined (_WIN64))
7786#undef None
7887#endif
7988
89+ // Macros to control exception handling and timeout features
90+ #ifndef NANOFLANN_DISABLE_EXCEPTIONS
91+ #define NANOFLANN_ENABLE_EXCEPTIONS 1
92+ #endif
93+
94+ #ifndef NANOFLANN_DISABLE_TIMEOUT
95+ #define NANOFLANN_ENABLE_TIMEOUT 1
96+ #endif
97+
8098namespace nanoflann
8199{
100+ /* * @addtogroup exceptions_grp Exception classes
101+ * @{ */
102+
103+ /* * Base class for all nanoflann exceptions */
104+ class Exception : public std ::exception
105+ {
106+ protected:
107+ std::string message_;
108+
109+ public:
110+ explicit Exception (const std::string& msg) : message_(msg) {}
111+
112+ ~Exception () noexcept override = default ;
113+
114+ const char * what () const noexcept override
115+ {
116+ return message_.c_str ();
117+ }
118+
119+ const std::string& getMessage () const noexcept
120+ {
121+ return message_;
122+ }
123+ };
124+
125+ /* * Exception thrown when a null pointer is encountered */
126+ class NullPointerException : public Exception
127+ {
128+ public:
129+ explicit NullPointerException (const std::string& msg) : Exception(msg) {}
130+ };
131+
132+ /* * Exception thrown when invalid data is encountered */
133+ class InvalidDataException : public Exception
134+ {
135+ public:
136+ explicit InvalidDataException (const std::string& msg) : Exception(msg) {}
137+ };
138+
139+ /* * Exception thrown when memory allocation fails */
140+ class MemoryAllocationException : public Exception
141+ {
142+ public:
143+ explicit MemoryAllocationException (const std::string& msg) : Exception(msg) {}
144+ };
145+
146+ /* * Exception thrown when an invalid parameter is provided */
147+ class InvalidParameterException : public Exception
148+ {
149+ public:
150+ explicit InvalidParameterException (const std::string& msg) : Exception(msg) {}
151+ };
152+
153+ /* * Exception thrown when a search operation times out */
154+ class SearchTimeoutException : public Exception
155+ {
156+ private:
157+ size_t searchedPoints_;
158+ size_t foundResults_;
159+
160+ public:
161+ SearchTimeoutException (const std::string& msg, size_t searchedPoints, size_t foundResults)
162+ : Exception(msg), searchedPoints_(searchedPoints), foundResults_(foundResults) {}
163+
164+ size_t getSearchedPoints () const noexcept
165+ {
166+ return searchedPoints_;
167+ }
168+
169+ size_t getFoundResults () const noexcept
170+ {
171+ return foundResults_;
172+ }
173+ };
174+
175+ /* * Exception thrown when concurrent modification is detected */
176+ class ConcurrentModificationException : public Exception
177+ {
178+ public:
179+ explicit ConcurrentModificationException (const std::string& msg) : Exception(msg) {}
180+ };
181+
182+ /* * Exception thrown when invalid point data (NaN or infinity) is encountered */
183+ class InvalidPointDataException : public Exception
184+ {
185+ public:
186+ explicit InvalidPointDataException (const std::string& msg) : Exception(msg) {}
187+ };
188+
189+ /* * @} */
190+
191+ /* * @addtogroup logging_grp Logging interface
192+ * @{ */
193+
194+ /* * Log level enum */
195+ enum class LogLevel
196+ {
197+ DEBUG,
198+ INFO,
199+ WARNING,
200+ ERROR
201+ };
202+
203+ /* * Log callback function type */
204+ typedef std::function<void (LogLevel level, const std::string& message)> LogCallback;
205+
206+ /* * Set a global log callback function */
207+ inline void setLogCallback (const LogCallback& callback)
208+ {
209+ static LogCallback globalLogCallback;
210+ globalLogCallback = callback;
211+ }
212+
213+ /* * Log a message using the global log callback */
214+ inline void logMessage (LogLevel level, const std::string& message)
215+ {
216+ static LogCallback globalLogCallback;
217+ if (globalLogCallback)
218+ {
219+ globalLogCallback (level, message);
220+ }
221+ }
222+
223+ /* * @}
224+
82225/** @addtogroup nanoflann_grp nanoflann C++ library for KD-trees
83226 * @{ */
84227
@@ -837,6 +980,18 @@ struct SearchParameters
837980 bool sorted; // !< only for radius search, require neighbours sorted by
838981 // !< distance (default: true)
839982};
983+
984+ /* * Search options for KDTreeSingleIndexAdaptor::findNeighbors() with timeout support */
985+ struct SearchParametersEx : public SearchParameters
986+ {
987+ SearchParametersEx (float eps_ = 0 , bool sorted_ = true , double timeout_seconds_ = 0 )
988+ : SearchParameters(eps_, sorted_), timeout_seconds(timeout_seconds_), start_time(0 )
989+ {
990+ }
991+
992+ double timeout_seconds; // !< maximum time allowed for search in seconds (0 = no timeout)
993+ double start_time; // !< start time of the search (internal use)
994+ };
840995/* * @} */
841996
842997/* * @addtogroup memalloc_grp Memory allocation
@@ -1723,7 +1878,16 @@ class KDTreeSingleIndexAdaptor
17231878 auto zero = static_cast <typename RESULTSET::DistanceType>(0 );
17241879 assign (dists, (DIM > 0 ? DIM : Base::dim_), zero);
17251880 DistanceType dist = this ->computeInitialDistances (*this , vec, dists);
1726- searchLevel (result, vec, Base::root_node_, dist, dists, epsError);
1881+
1882+ // Check if searchParams is actually a SearchParametersEx
1883+ const SearchParametersEx* searchParamsEx = dynamic_cast <const SearchParametersEx*>(&searchParams);
1884+ double startTime = 0 ;
1885+ if (searchParamsEx && searchParamsEx->timeout_seconds > 0 )
1886+ {
1887+ startTime = getCurrentTime ();
1888+ }
1889+
1890+ searchLevel (result, vec, Base::root_node_, dist, dists, epsError, startTime, searchParamsEx ? searchParamsEx->timeout_seconds : 0 );
17271891
17281892 if (searchParams.sorted ) result.sort ();
17291893
@@ -1885,8 +2049,15 @@ class KDTreeSingleIndexAdaptor
18852049 bool searchLevel (
18862050 RESULTSET& result_set, const ElementType* vec, const NodePtr node,
18872051 DistanceType mindist, distance_vector_t & dists,
1888- const float epsError) const
2052+ const float epsError, const double startTime = 0 , const double timeoutSeconds = 0 ) const
18892053 {
2054+ // Check for timeout
2055+ if (timeoutSeconds > 0 && getCurrentTime () - startTime > timeoutSeconds)
2056+ {
2057+ // Timeout reached, stop searching
2058+ return false ;
2059+ }
2060+
18902061 // If this is a leaf node, then do check and return.
18912062 // If they are equal, both pointers are nullptr.
18922063 if (node->child1 == node->child2 )
@@ -1895,6 +2066,13 @@ class KDTreeSingleIndexAdaptor
18952066 for (Offset i = node->node_type .lr .left ;
18962067 i < node->node_type .lr .right ; ++i)
18972068 {
2069+ // Check for timeout in loop
2070+ if (timeoutSeconds > 0 && getCurrentTime () - startTime > timeoutSeconds)
2071+ {
2072+ // Timeout reached, stop searching
2073+ return false ;
2074+ }
2075+
18982076 const IndexType accessor = Base::vAcc_[i]; // reorder... : i;
18992077 DistanceType dist = distance_.evalMetric (
19002078 vec, accessor, (DIM > 0 ? DIM : Base::dim_));
@@ -1936,7 +2114,7 @@ class KDTreeSingleIndexAdaptor
19362114 }
19372115
19382116 /* Call recursively to search next level down. */
1939- if (!searchLevel (result_set, vec, bestChild, mindist, dists, epsError))
2117+ if (!searchLevel (result_set, vec, bestChild, mindist, dists, epsError, startTime, timeoutSeconds ))
19402118 {
19412119 // the resultset doesn't want to receive any more points, we're done
19422120 // searching!
@@ -1949,7 +2127,7 @@ class KDTreeSingleIndexAdaptor
19492127 if (mindist * epsError <= result_set.worstDist ())
19502128 {
19512129 if (!searchLevel (
1952- result_set, vec, otherChild, mindist, dists, epsError))
2130+ result_set, vec, otherChild, mindist, dists, epsError, startTime, timeoutSeconds ))
19532131 {
19542132 // the resultset doesn't want to receive any more points, we're
19552133 // done searching!
@@ -2194,7 +2372,16 @@ class KDTreeSingleIndexDynamicAdaptor_
21942372 dists, (DIM > 0 ? DIM : Base::dim_),
21952373 static_cast <typename distance_vector_t ::value_type>(0 ));
21962374 DistanceType dist = this ->computeInitialDistances (*this , vec, dists);
2197- searchLevel (result, vec, Base::root_node_, dist, dists, epsError);
2375+
2376+ // Check if searchParams is actually a SearchParametersEx
2377+ const SearchParametersEx* searchParamsEx = dynamic_cast <const SearchParametersEx*>(&searchParams);
2378+ double startTime = 0 ;
2379+ if (searchParamsEx && searchParamsEx->timeout_seconds > 0 )
2380+ {
2381+ startTime = getCurrentTime ();
2382+ }
2383+
2384+ searchLevel (result, vec, Base::root_node_, dist, dists, epsError, startTime, searchParamsEx ? searchParamsEx->timeout_seconds : 0 );
21982385 return result.full ();
21992386 }
22002387
@@ -2313,8 +2500,15 @@ class KDTreeSingleIndexDynamicAdaptor_
23132500 void searchLevel (
23142501 RESULTSET& result_set, const ElementType* vec, const NodePtr node,
23152502 DistanceType mindist, distance_vector_t & dists,
2316- const float epsError) const
2503+ const float epsError, const double startTime = 0 , const double timeoutSeconds = 0 ) const
23172504 {
2505+ // Check for timeout
2506+ if (timeoutSeconds > 0 && getCurrentTime () - startTime > timeoutSeconds)
2507+ {
2508+ // Timeout reached, stop searching
2509+ return ;
2510+ }
2511+
23182512 // If this is a leaf node, then do check and return.
23192513 // If they are equal, both pointers are nullptr.
23202514 if (node->child1 == node->child2 )
@@ -2323,6 +2517,13 @@ class KDTreeSingleIndexDynamicAdaptor_
23232517 for (Offset i = node->node_type .lr .left ;
23242518 i < node->node_type .lr .right ; ++i)
23252519 {
2520+ // Check for timeout in loop
2521+ if (timeoutSeconds > 0 && getCurrentTime () - startTime > timeoutSeconds)
2522+ {
2523+ // Timeout reached, stop searching
2524+ return ;
2525+ }
2526+
23262527 const IndexType index = Base::vAcc_[i]; // reorder... : i;
23272528 if (treeIndex_[index] == -1 ) continue ;
23282529 DistanceType dist = distance_.evalMetric (
@@ -2368,14 +2569,14 @@ class KDTreeSingleIndexDynamicAdaptor_
23682569 }
23692570
23702571 /* Call recursively to search next level down. */
2371- searchLevel (result_set, vec, bestChild, mindist, dists, epsError);
2572+ searchLevel (result_set, vec, bestChild, mindist, dists, epsError, startTime, timeoutSeconds );
23722573
23732574 DistanceType dst = dists[idx];
23742575 mindist = mindist + cut_dist - dst;
23752576 dists[idx] = cut_dist;
23762577 if (mindist * epsError <= result_set.worstDist ())
23772578 {
2378- searchLevel (result_set, vec, otherChild, mindist, dists, epsError);
2579+ searchLevel (result_set, vec, otherChild, mindist, dists, epsError, startTime, timeoutSeconds );
23792580 }
23802581 dists[idx] = dst;
23812582 }
0 commit comments