-
Notifications
You must be signed in to change notification settings - Fork 25
Expand file tree
/
Copy pathcover.py
More file actions
661 lines (558 loc) · 25.1 KB
/
cover.py
File metadata and controls
661 lines (558 loc) · 25.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
"""
Open cover construction for the Mapper algorithm.
An open cover is a collection of open subsets of a dataset whose union spans
the whole dataset. Unlike clustering, open subsets do not need to be disjoint.
Indeed, the overlaps of the open subsets define the edges of the Mapper graph.
"""
from __future__ import annotations
import math
from typing import Any, Callable, Generic, Iterator, Literal, Optional, TypeVar, Union
import numpy as np
from numpy.typing import NDArray
from tdamapper._common import ParamsMixin, warn_user
from tdamapper.core import proximity_net
from tdamapper.protocols import ArrayRead, Metric
from tdamapper.utils.metrics import MetricLiteral, chebyshev, get_metric
from tdamapper.utils.vptree import PivotingStrategy, VPTree, VPTreeKind
T = TypeVar("T")
S = TypeVar("S")
T_contra = TypeVar("T_contra", contravariant=True)
S_contra = TypeVar("S_contra", contravariant=True)
CubicalAlgorithm = Literal["standard", "proximity"]
class _Pullback(Generic[S_contra, T_contra]):
"""
Pullback pseudo-metric function.
This class is used to adapt a metric function that operates on a
transformed space to work with the original space. It applies a function
to the input data before computing the distance, effectively pulling back
the metric to the original space.
Given a function :math:`f: X \to Y` and a metric
:math:`d: Y \times Y \to \\mathbb{R}`,
this class defines a new pseudo-metric
:math:`d': X \times X \to \\mathbb{R}` such that:
:math:`d'(x_1, x_2) = d(f(x_1), f(x_2))`.
When :math:`f` is injective, this pseudo-metric :math:`d'` is a true
metric. If :math:`f` is not injective, it is a pseudo-metric, meaning it
may not satisfy the identity of two objects :math:`x_1`, :math:`x_2` with
:math:`d'(x_1, x_2) = 0`.
:param fun: A function that transforms the input data.
:param dist: A metric function that operates on the transformed data.
"""
def __init__(
self, fun: Callable[[S_contra], T_contra], dist: Metric[T_contra]
) -> None:
self.fun = fun
self.dist = dist
def __call__(self, x: S_contra, y: S_contra) -> float:
"""
Compute the distance between two points in the original space
using the pullback metric.
This method applies the transformation function to both points and
then computes the distance using the provided metric function.
:param x: A point in the original space.
:param y: Another point in the original space.
:return: The distance between the transformed points in the metric space.
"""
return self.dist(self.fun(x), self.fun(y))
def _snd(x: tuple[T, ...]) -> T:
"""
Extract the second element from a tuple.
"""
return x[1]
class BallCover(ParamsMixin, Generic[T_contra]):
"""
Cover algorithm based on `ball proximity function`, which covers data with
open balls of fixed radius.
An open ball is a set of points within a specified distance from a center
point. This class maps each point to its corresponding open ball with a
fixed radius centered on the point itself.
:param radius: The radius of the open balls. Must be a positive value.
:param metric: The metric used to define the distance between points.
Accepts any value compatible with `tdamapper.utils.metrics.get_metric`.
:param metric_params: Additional parameters for the metric function, to be
passed to `tdamapper.utils.metrics.get_metric`.
:param kind: Specifies whether to use a flat or a hierarchical vantage
point tree.
:param leaf_capacity: The maximum number of points in a leaf node of the
vantage point tree. Must be a positive value.
:param leaf_radius: The radius of the leaf nodes. If not specified, it
defaults to the value of `radius`. Must be a positive value.
:param pivoting: The method used for pivoting in the vantage point tree.
"""
_radius: float
_data: list[tuple[int, T_contra]]
_vptree: VPTree[tuple[int, T_contra]]
def __init__(
self,
radius: float = 1.0,
metric: Union[MetricLiteral, Metric[Any]] = "euclidean",
metric_params: Optional[dict[str, Any]] = None,
kind: VPTreeKind = "flat",
leaf_capacity: int = 1,
leaf_radius: Optional[float] = None,
pivoting: PivotingStrategy = "disabled",
) -> None:
self.radius = radius
self.metric = metric
self.metric_params = metric_params
self.kind = kind
self.leaf_capacity = leaf_capacity
self.leaf_radius = leaf_radius
self.pivoting = pivoting
def fit(self, X: ArrayRead[T_contra]) -> BallCover[T_contra]:
"""
Train internal parameters.
This method creates a vptree on the dataset in order to perform fast
range queries in the func:`tdamapper.cover.BallCover.search`
method.
:param X: A dataset of n points.
:return: The object itself.
"""
metric = get_metric(self.metric, **(self.metric_params or {}))
self._radius = self.radius
self._data = list(enumerate(X))
self._vptree = VPTree(
self._data,
metric=_Pullback(_snd, metric),
kind=self.kind,
leaf_capacity=self.leaf_capacity,
leaf_radius=self.leaf_radius or self.radius,
pivoting=self.pivoting,
)
return self
def search(self, x: T_contra) -> list[int]:
"""
Return a list of neighbors for the query point.
This method uses the internal vptree to perform fast range queries.
:param x: A query point for which we want to find neighbors.
:return: The indices of the neighbors contained in the dataset.
"""
if self._vptree is None:
return []
neighs = self._vptree.ball_search(
(-1, x),
self._radius,
inclusive=False,
)
return [x for (x, _) in neighs]
def apply(self, X: ArrayRead[T_contra]) -> Iterator[list[int]]:
"""
Covers the dataset using proximity-net.
This function returns a generator that yields each element of the
open cover as a list of ids. The ids are the indices of the points
in the original dataset.
:param X: A dataset of n points.
:return: A generator of lists of ids.
"""
return proximity_net(self, X)
class KNNCover(ParamsMixin, Generic[T_contra]):
"""
Cover algorithm based on `KNN proximity function`, which covers data using
k-nearest neighbors (KNN).
This class maps each point to the set of the k nearest neighbors to the
point itself.
:param neighbors: The number of neighbors to use for the KNN Proximity
function, must be positive and less than the length of the dataset.
:param metric: The metric used to define the distance between points.
Accepts any value compatible with `tdamapper.utils.metrics.get_metric`.
:param metric_params: Additional parameters for the metric function, to be
passed to `tdamapper.utils.metrics.get_metric`.
:param kind: Specifies whether to use a flat or a hierarchical vantage
point tree.
:param leaf_capacity: The maximum number of points in a leaf node of the
vantage point tree. If not specified, it defaults to the value of
`neighbors`. Must be a positive value.
:param leaf_radius: The radius of the leaf nodes. Must be a positive value.
:param pivoting: The method used for pivoting in the vantage point tree.
"""
_neighbors: int
_data: list[tuple[int, T_contra]]
_vptree: VPTree[tuple[int, T_contra]]
def __init__(
self,
neighbors: int = 1,
metric: Union[MetricLiteral, Metric[Any]] = "euclidean",
metric_params: Optional[dict[str, Any]] = None,
kind: VPTreeKind = "flat",
leaf_capacity: Optional[int] = None,
leaf_radius: float = 0.0,
pivoting: PivotingStrategy = "disabled",
) -> None:
self.neighbors = neighbors
self.metric = metric
self.metric_params = metric_params
self.kind = kind
self.leaf_capacity = leaf_capacity
self.leaf_radius = leaf_radius
self.pivoting = pivoting
def fit(self, X: ArrayRead[T_contra]) -> KNNCover[T_contra]:
"""
Train internal parameters.
This method creates a vptree on the dataset in order to perform fast
KNN queries in the func:`tdamapper.cover.BallCover.search`
method.
:param X: A dataset of n points.
:return: The object itself.
"""
metric = get_metric(self.metric, **(self.metric_params or {}))
self._neighbors = self.neighbors
self._data = list(enumerate(X))
self._vptree = VPTree(
self._data,
metric=_Pullback(_snd, metric),
kind=self.kind,
leaf_capacity=self.leaf_capacity or self.neighbors,
leaf_radius=self.leaf_radius,
pivoting=self.pivoting,
)
return self
def search(self, x: T_contra) -> list[int]:
"""
Return a list of neighbors for the query point.
This method queries the internal vptree in order to perform fast KNN
queries.
:param x: A query point for which we want to find neighbors.
:return: The indices of the neighbors contained in the dataset.
"""
if self._vptree is None:
return []
neighs = self._vptree.knn_search((-1, x), self._neighbors)
return [x for (x, _) in neighs]
def apply(self, X: ArrayRead[T_contra]) -> Iterator[list[int]]:
"""
Covers the dataset using proximity-net.
This function returns a generator that yields each element of the
open cover as a list of ids. The ids are the indices of the points
in the original dataset.
:param X: A dataset of n points.
:return: A generator of lists of ids.
"""
return proximity_net(self, X)
class BaseCubicalCover:
"""
Base class for cubical cover algorithms, which cover data with open
hypercubes of uniform size and overlap. This class provides the basic
functionality for cubical covers, including the initialization of parameters
and the methods for computing the center of a hypercube and its overlap.
A hypercube is a multidimensional generalization of a square or a cube.
The size and overlap of the hypercubes are determined by the number of
intervals and the overlap fraction parameters. This class maps each point
to the hypercube with the nearest center.
:param n_intervals: The number of intervals to use for each dimension.
Must be positive and less than or equal to the length of the dataset.
:param overlap_frac: The fraction of overlap between adjacent intervals on
each dimension, must be in the range (0.0, 0.5]. If not specified, the
overlap_frac is computed such that the volume of the overlap within
each hypercube is half the total volume.
:param kind: Specifies whether to use a flat or a hierarchical vantage
point tree.
:param leaf_capacity: The maximum number of points in a leaf node of the
vantage point tree. Must be a positive value.
:param leaf_radius: The radius of the leaf nodes. If not specified, it
defaults to the value of `radius`. Must be a positive value.
:param pivoting: The method used for pivoting in the vantage point tree.
"""
_overlap_frac: float
_n_intervals: int
_min: NDArray[np.float_]
_max: NDArray[np.float_]
_delta: NDArray[np.float_]
_cover: BallCover[NDArray[np.float_]]
def __init__(
self,
n_intervals: int = 1,
overlap_frac: Optional[float] = None,
kind: VPTreeKind = "flat",
leaf_capacity: int = 1,
leaf_radius: Optional[float] = None,
pivoting: PivotingStrategy = "disabled",
) -> None:
self.n_intervals = n_intervals
self.overlap_frac = overlap_frac
self.kind = kind
self.leaf_capacity = leaf_capacity
self.leaf_radius = leaf_radius
self.pivoting = pivoting
def _get_center(
self, x: NDArray[np.float_]
) -> tuple[tuple[float], NDArray[np.float_]]:
offset = self._offset(x)
center = self._phi(x)
return tuple(offset), center
def _get_overlap_frac(self, dim: int, overlap_vol_frac: float) -> float:
beta = math.pow(1.0 - overlap_vol_frac, 1.0 / dim)
return 1.0 - 1.0 / (2.0 - beta)
def _offset(self, x: NDArray[np.float_]) -> NDArray[np.float_]:
return np.minimum(self._n_intervals - 1, np.floor(self._gamma_n(x)))
def _phi(self, x: NDArray[np.float_]) -> NDArray[np.float_]:
offset = self._offset(x)
return self._gamma_n_inv(0.5 + offset)
def _gamma_n(self, x: NDArray[np.float_]) -> NDArray[np.float_]:
return self._n_intervals * (x - self._min) / self._delta
def _gamma_n_inv(self, x: NDArray[np.float_]) -> NDArray[np.float_]:
return self._min + self._delta * x / self._n_intervals
def _get_bounds(
self, X: NDArray[np.float_]
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
if (X is None) or len(X) == 0:
raise ValueError("The dataset is empty.")
_min, _max = X[0], X[0]
eps = np.finfo(np.float64).eps
_min = np.min(X, axis=0)
_max = np.max(X, axis=0)
_delta = _max - _min
_delta[(_delta >= -eps) & (_delta <= eps)] = self._n_intervals
return _min, _max, _delta
def fit(self, X: ArrayRead[NDArray[np.float_]]) -> BaseCubicalCover:
"""
Train internal parameters.
This method builds an internal :class:`tdamapper.cover.BallCover`
attribute that allows efficient queries of the dataset.
:param X: A dataset of n points.
:return: The object itself.
"""
if len(X) == 0:
return self
X_ = np.asarray(X).reshape(len(X), -1).astype(float)
if self.overlap_frac is None:
dim = 1 if X_.ndim == 1 else X_.shape[1]
self._overlap_frac = self._get_overlap_frac(dim, 0.5)
else:
self._overlap_frac = self.overlap_frac
self._n_intervals = self.n_intervals
if self._overlap_frac <= 0.0:
raise ValueError("The parameter overlap_frac is expected to be > 0.0")
if self._overlap_frac > 0.5:
warn_user("The parameter overlap_frac is expected to be <= 0.5")
self._min, self._max, self._delta = self._get_bounds(X_)
radius = 1.0 / (2.0 - 2.0 * self._overlap_frac)
self._cover = BallCover(
radius,
metric=_Pullback(self._gamma_n, chebyshev()),
kind=self.kind,
leaf_capacity=self.leaf_capacity,
leaf_radius=self.leaf_radius,
pivoting=self.pivoting,
)
self._cover.fit(X_)
return self
def search(self, x: NDArray[np.float_]) -> list[int]:
"""
Return a list of neighbors for the query point.
This method takes a target point as input and returns the hypercube
whose center is closest to the target point.
:param x: A query point for which we want to find neighbors.
:return: The indices of the neighbors contained in the dataset.
"""
center = self._phi(x)
return self._cover.search(center)
class ProximityCubicalCover(BaseCubicalCover, ParamsMixin):
"""
Cover algorithm based on the `cubical proximity function`, which covers
data with open hypercubes of uniform size and overlap. The cubical cover is
obtained by selecting a subsect of all the hypercubes that intersect the
dataset using proximity net (see :class:`tdamapper.core.Proximity`).
For an open cover containing all the hypercubes interecting the dataset
use :class:`tdamapper.core.StandardCubicalCover`.
A hypercube is a multidimensional generalization of a square or a cube.
The size and overlap of the hypercubes are determined by the number of
intervals and the overlap fraction parameters. This class maps each point
to the hypercube with the nearest center.
:param n_intervals: The number of intervals to use for each dimension.
Must be positive and less than or equal to the length of the dataset.
:param overlap_frac: The fraction of overlap between adjacent intervals on
each dimension, must be in the range (0.0, 0.5]. If not specified, the
overlap_frac is computed such that the volume of the overlap within
each hypercube is half the total volume.
:param kind: Specifies whether to use a flat or a hierarchical vantage
point tree.
:param leaf_capacity: The maximum number of points in a leaf node of the
vantage point tree. Must be a positive value.
:param leaf_radius: The radius of the leaf nodes. If not specified, it
defaults to the value of `radius`. Must be a positive value.
:param pivoting: The method used for pivoting in the vantage point tree.
"""
def __init__(
self,
n_intervals: int = 1,
overlap_frac: Optional[float] = None,
kind: VPTreeKind = "flat",
leaf_capacity: int = 1,
leaf_radius: Optional[float] = None,
pivoting: PivotingStrategy = "disabled",
) -> None:
super().__init__(
n_intervals=n_intervals,
overlap_frac=overlap_frac,
kind=kind,
leaf_capacity=leaf_capacity,
leaf_radius=leaf_radius,
pivoting=pivoting,
)
def apply(self, X: ArrayRead[NDArray[np.float_]]) -> Iterator[list[int]]:
"""
Covers the dataset using proximity-net.
This function returns a generator that yields each element of the
open cover as a list of ids. The ids are the indices of the points
in the original dataset.
:param X: A dataset of n points.
:return: A generator of lists of ids.
"""
return proximity_net(self, X)
class StandardCubicalCover(BaseCubicalCover, ParamsMixin):
"""
Cover algorithm based on the standard open cover, which covers data with
open hypercubes of uniform size and overlap. The standard cover is
obtained by selecting all the hypercubes that intersect the dataset.
A hypercube is a multidimensional generalization of a square or a cube.
The size and overlap of the hypercubes are determined by the number of
intervals and the overlap fraction parameters. This class maps each point
to the hypercube with the nearest center.
:param n_intervals: The number of intervals to use for each dimension.
Must be positive and less than or equal to the length of the dataset.
:param overlap_frac: The fraction of overlap between adjacent intervals on
each dimension, must be in the range (0.0, 0.5]. If not specified, the
overlap_frac is computed such that the volume of the overlap within
each hypercube is half the total volume.
:param kind: Specifies whether to use a flat or a hierarchical vantage
point tree.
:param leaf_capacity: The maximum number of points in a leaf node of the
vantage point tree. Must be a positive value.
:param leaf_radius: The radius of the leaf nodes. If not specified, it
defaults to the value of `radius`. Must be a positive value.
:param pivoting: The method used for pivoting in the vantage point tree.
"""
def __init__(
self,
n_intervals: int = 1,
overlap_frac: Optional[float] = None,
kind: VPTreeKind = "flat",
leaf_capacity: int = 1,
leaf_radius: Optional[float] = None,
pivoting: PivotingStrategy = "disabled",
) -> None:
super().__init__(
n_intervals=n_intervals,
overlap_frac=overlap_frac,
kind=kind,
leaf_capacity=leaf_capacity,
leaf_radius=leaf_radius,
pivoting=pivoting,
)
def _landmarks(
self, X: ArrayRead[NDArray[np.float_]]
) -> dict[tuple[float], NDArray[np.float_]]:
lmrks = {}
for x in X:
lmrk, _ = self._get_center(x)
if lmrk not in lmrks:
lmrks[lmrk] = x
return lmrks
def apply(self, X: ArrayRead[NDArray[np.float_]]) -> Iterator[list[int]]:
"""
Covers the dataset using landmarks.
This function yields all the hypercubes intersecting the dataset.
This function returns a generator that yields each element of the
open cover as a list of ids. The ids are the indices of the points
in the original dataset.
:param X: A dataset of n points.
:return: A generator of lists of ids.
"""
self.fit(X)
lmrks_to_cover = self._landmarks(X)
while lmrks_to_cover:
_, x = lmrks_to_cover.popitem()
neigh_ids = self.search(x)
if neigh_ids:
yield neigh_ids
class CubicalCover(ParamsMixin):
"""
Wrapper class for cubical cover algorithms, which cover data with open
hypercubes of uniform size and overlap. This class delegates its methods to
either :class:`tdamapper.cover.StandardCubicalCover` or
:class:`tdamapper.cover.ProximityCubicalCover`, based on the `algorithm`
parameter.
A hypercube is a multidimensional generalization of a square or a cube.
The size and overlap of the hypercubes are determined by the number of
intervals and the overlap fraction parameters.
:param n_intervals: The number of intervals to use for each dimension.
Must be positive and less than or equal to the length of the dataset.
:param overlap_frac: The fraction of overlap between adjacent intervals on
each dimension, must be in the range (0.0, 0.5]. If not specified, the
overlap_frac is computed such that the volume of the overlap within
each hypercube is half the total volume.
:param algorithm: Specifies whether to use standard cubical cover, as in
:class:`tdamapper.cover.StandardCubicalCover` or proximity cubical
cover, as in :class:`tdamapper.cover.ProximityCubicalCover`.
:param kind: Specifies whether to use a flat or a hierarchical vantage
point tree.
:param leaf_capacity: The maximum number of points in a leaf node of the
vantage point tree. Must be a positive value.
:param leaf_radius: The radius of the leaf nodes. If not specified, it
defaults to the value of `radius`. Must be a positive value.
:param pivoting: The method used for pivoting in the vantage point tree.
"""
_cubical_cover: Union[ProximityCubicalCover, StandardCubicalCover]
def __init__(
self,
n_intervals: int = 1,
overlap_frac: Optional[float] = None,
algorithm: CubicalAlgorithm = "proximity",
kind: VPTreeKind = "flat",
leaf_capacity: int = 1,
leaf_radius: Optional[float] = None,
pivoting: PivotingStrategy = "disabled",
):
self.n_intervals = n_intervals
self.overlap_frac = overlap_frac
self.algorithm = algorithm
self.kind = kind
self.leaf_capacity = leaf_capacity
self.leaf_radius = leaf_radius
self.pivoting = pivoting
def _get_cubical_cover(self) -> Union[ProximityCubicalCover, StandardCubicalCover]:
params: dict[str, Any] = dict(
n_intervals=self.n_intervals,
overlap_frac=self.overlap_frac,
kind=self.kind,
leaf_capacity=self.leaf_capacity,
leaf_radius=self.leaf_radius,
pivoting=self.pivoting,
)
if self.algorithm == "proximity":
return ProximityCubicalCover(**params)
if self.algorithm == "standard":
return StandardCubicalCover(**params)
raise ValueError(
"The only possible values for algorithm are 'standard' and 'proximity'."
)
def fit(self, X: ArrayRead[NDArray[np.float_]]) -> CubicalCover:
"""
Train internal parameters.
This method delegates to the :func:`fit` method of the internal cubical
cover used.
:param X: A dataset of n points.
:return: The object itself.
"""
self._cubical_cover = self._get_cubical_cover()
self._cubical_cover.fit(X)
return self
def search(self, x: NDArray[np.float_]) -> list[int]:
"""
Return a list of neighbors for the query point.
This method delegates to the `search` method of the internal cubical
cover used.
:param x: A query point for which we want to find neighbors.
:return: The indices of the neighbors contained in the dataset.
"""
return self._cubical_cover.search(x)
def apply(self, X: ArrayRead[NDArray[np.float_]]) -> Iterator[list[int]]:
"""
Covers the dataset using hypercubes.
This method delegates to the `apply` method of the internal cubical
cover used.
:param X: A dataset of n points.
:return: A generator of lists of ids.
"""
self._cubical_cover = self._get_cubical_cover()
return self._cubical_cover.apply(X)