@@ -230,6 +230,10 @@ class FarEnoughSampleFilter:
230230
231231 :param X: Matrix of existing sample points (n x d).
232232 :param tol: Minimum distance threshold.
233+ :param approach: Method to select points when multiple candidates are close
234+ to each other. Options are:
235+ "independent_set" (default) which uses a maximum independent set
236+ algorithm, and "greedy" which uses a simple greedy approach.
233237
234238 .. attribute:: tree
235239
@@ -239,11 +243,17 @@ class FarEnoughSampleFilter:
239243 .. attribute:: tol
240244
241245 Minimum distance threshold. Points closer than this are filtered out.
246+
247+ .. attribute:: approach
248+
249+ Method to select points when multiple candidates are close to each
250+ other.
242251 """
243252
244- def __init__ (self , X , tol ):
253+ def __init__ (self , X , tol , approach = "independent_set" ):
245254 self .tree = KDTree (X )
246255 self .tol = tol
256+ self .approach = approach
247257
248258 def is_far_enough (self , x ):
249259 """Check if a point is far enough from existing samples.
@@ -254,6 +264,28 @@ def is_far_enough(self, x):
254264 dist , _ = self .tree .query (x .reshape (1 , - 1 ))
255265 return dist [0 ] >= self .tol
256266
267+ def max_independent_set (self , X ):
268+ dist = cdist (X , X )
269+ np .fill_diagonal (dist , np .inf )
270+ g = nx .Graph ()
271+ g .add_nodes_from (range (len (X )))
272+ g .add_edges_from (
273+ [
274+ (i , j )
275+ for i in range (len (X ))
276+ for j in range (i + 1 , len (X ))
277+ if dist [i , j ] < self .tol
278+ ]
279+ )
280+ return maximum_independent_set (g )
281+
282+ def greedy_independent_set (self , X ):
283+ idx = []
284+ for i in range (len (X )):
285+ if all (np .linalg .norm (X [i ] - X [j ]) >= self .tol for j in idx ):
286+ idx .append (i )
287+ return idx
288+
257289 def indices (self , Xc ):
258290 """Filter candidates based on minimum distance criterion.
259291
@@ -266,19 +298,18 @@ def indices(self, Xc):
266298 Xc0 = Xc [mask0 ]
267299
268300 # Find the maximum independent set among the remaining points
269- dist = cdist (Xc0 , Xc0 )
270- np .fill_diagonal (dist , np .inf )
271- g = nx .Graph ()
272- g .add_nodes_from (range (len (Xc0 )))
273- g .add_edges_from (
274- [
275- (i , j )
276- for i in range (len (Xc0 ))
277- for j in range (i + 1 , len (Xc0 ))
278- if dist [i , j ] < self .tol
279- ]
280- )
281- idx = maximum_independent_set (g )
301+ if self .approach == "independent_set" :
302+ try :
303+ idx = self .max_independent_set (Xc0 )
304+ except Exception as e :
305+ logger .warning (
306+ "Error in maximum independent set computation: %s. "
307+ "Falling back to a greedy approach." ,
308+ e ,
309+ )
310+ idx = self .greedy_independent_set (Xc0 )
311+ else :
312+ idx = self .greedy_independent_set (Xc0 )
282313
283314 # Recover original indices
284315 original_indices = np .where (mask0 )[0 ]
0 commit comments