usearch 0.20.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,20 +1,16 @@
1
1
  import os
2
- import importlib.util
3
2
 
4
3
  import pytest
5
4
  import numpy as np
6
5
 
7
6
  from usearch.io import load_matrix, save_matrix
8
7
  from usearch.eval import random_vectors
9
- from usearch.numba import jit as njit
10
8
 
11
9
  from usearch.index import (
12
10
  Index,
13
11
  SparseIndex,
14
12
  MetricKind,
15
13
  ScalarKind,
16
- MetricSignature,
17
- CompiledMetric,
18
14
  Matches,
19
15
  )
20
16
  from usearch.index import (
@@ -35,7 +31,6 @@ index_types = [
35
31
  numpy_types = [np.float32, np.float64, np.float16]
36
32
 
37
33
  connectivity_options = [3, 13, 50, DEFAULT_CONNECTIVITY]
38
- jit_options = [False, True] if importlib.util.find_spec("spam") is not None else [False]
39
34
  continuous_metrics = [
40
35
  MetricKind.Cos,
41
36
  MetricKind.L2sq,
@@ -50,6 +45,12 @@ hash_metrics = [
50
45
  @pytest.mark.parametrize("rows", batch_sizes)
51
46
  @pytest.mark.parametrize("cols", dimensions)
52
47
  def test_serializing_fbin_matrix(rows: int, cols: int):
48
+ """
49
+ Test the serialization of floating point binary matrix.
50
+
51
+ :param int rows: The number of rows in the matrix.
52
+ :param int cols: The number of columns in the matrix.
53
+ """
53
54
  original = np.random.rand(rows, cols).astype(np.float32)
54
55
  save_matrix(original, "tmp.fbin")
55
56
  reconstructed = load_matrix("tmp.fbin")
@@ -60,6 +61,12 @@ def test_serializing_fbin_matrix(rows: int, cols: int):
60
61
  @pytest.mark.parametrize("rows", batch_sizes)
61
62
  @pytest.mark.parametrize("cols", dimensions)
62
63
  def test_serializing_ibin_matrix(rows: int, cols: int):
64
+ """
65
+ Test the serialization of integer binary matrix.
66
+
67
+ :param int rows: The number of rows in the matrix.
68
+ :param int cols: The number of columns in the matrix.
69
+ """
63
70
  original = np.random.randint(0, rows + 1, size=(rows, cols)).astype(np.int32)
64
71
  save_matrix(original, "tmp.ibin")
65
72
  reconstructed = load_matrix("tmp.ibin")
@@ -72,17 +79,13 @@ def test_serializing_ibin_matrix(rows: int, cols: int):
72
79
  @pytest.mark.parametrize("index_type", index_types)
73
80
  @pytest.mark.parametrize("numpy_type", numpy_types)
74
81
  @pytest.mark.parametrize("connectivity", connectivity_options)
75
- @pytest.mark.parametrize("jit", jit_options)
76
82
  def test_index(
77
83
  ndim: int,
78
84
  metric: MetricKind,
79
85
  index_type: ScalarKind,
80
86
  numpy_type: str,
81
87
  connectivity: int,
82
- jit: bool,
83
88
  ):
84
- if jit:
85
- metric = njit(ndim, metric, index_type)
86
89
  index = Index(
87
90
  metric=metric,
88
91
  ndim=ndim,
@@ -95,7 +98,6 @@ def test_index(
95
98
  assert index.connectivity == connectivity
96
99
 
97
100
  vector = random_vectors(count=1, ndim=ndim, dtype=numpy_type).flatten()
98
-
99
101
  index.add(42, vector)
100
102
 
101
103
  assert 42 in index
@@ -115,9 +117,27 @@ def test_index(
115
117
  assert matches[0] == 42
116
118
  assert distances[0] == pytest.approx(0, abs=1e-3)
117
119
 
120
+ assert index.max_level >= 0
121
+ assert index.levels_stats.nodes >= 1
122
+ assert index.level_stats(0).nodes == 1
123
+ assert str(index).startswith("usearch.")
124
+
125
+ # Try removals
126
+ other_vector = random_vectors(count=1, ndim=ndim, dtype=numpy_type).flatten()
127
+ index.add(43, other_vector)
128
+ assert len(index) == 2
129
+ index.remove(43)
130
+ assert len(index) == 1
131
+
118
132
  index.save("tmp.usearch")
133
+
134
+ # Re-populate cleared index
119
135
  index.clear()
120
136
  assert len(index) == 0
137
+ index.add(42, vector)
138
+ assert len(index) == 1
139
+ matches, distances, count = index.search(vector, 10)
140
+ assert count == 1
121
141
 
122
142
  index.load("tmp.usearch")
123
143
  assert len(index) == 1
@@ -127,6 +147,10 @@ def test_index(
127
147
  assert len(index) == 1
128
148
  assert len(index[42]) == ndim
129
149
 
150
+ index_copy = index.copy()
151
+ assert len(index_copy) == 1
152
+ assert len(index_copy[42]) == ndim
153
+
130
154
  # Cleanup
131
155
  os.remove("tmp.usearch")
132
156
 
@@ -148,15 +172,19 @@ def test_index_batch(
148
172
  labels = np.arange(batch_size)
149
173
  vectors = random_vectors(count=batch_size, ndim=ndim, dtype=numpy_type)
150
174
 
151
- index.add(labels, vectors)
175
+ index.add(labels, vectors, threads=2)
152
176
  assert len(index) == batch_size
153
177
  assert np.allclose(index.get_vectors(labels).astype(numpy_type), vectors, atol=0.1)
154
178
 
155
- matches: Matches = index.search(vectors, 10)
179
+ matches: Matches = index.search(vectors, 10, threads=2)
156
180
  assert matches.labels.shape[0] == matches.distances.shape[0]
157
181
  assert matches.counts.shape[0] == batch_size
158
182
  assert np.all(np.sort(index.labels) == np.sort(labels))
159
183
 
184
+ assert index.max_level >= 0 # TODO: This should be 1
185
+ assert index.levels_stats.nodes >= batch_size
186
+ assert index.level_stats(0).nodes == batch_size
187
+
160
188
  index.save("tmp.usearch")
161
189
  index.clear()
162
190
  assert len(index) == 0
@@ -205,235 +233,11 @@ def test_exact_recall(
205
233
  for i in range(batch_size):
206
234
  assert found_labels[i, 0] == i
207
235
 
208
-
209
- @pytest.mark.parametrize("ndim", dimensions)
210
- @pytest.mark.parametrize("batch_size", batch_sizes)
211
- def test_index_numba(ndim: int, batch_size: int):
212
- try:
213
- from numba import cfunc, types, carray
214
- except ImportError:
215
- return
216
-
217
- # Showcases how to use Numba to JIT-compile similarity measures for USearch.
218
- # https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#c-callbacks
219
- signature_two_args = types.float32(
220
- types.CPointer(types.float32),
221
- types.CPointer(types.float32),
222
- )
223
- signature_three_args = types.float32(
224
- types.CPointer(types.float32),
225
- types.CPointer(types.float32),
226
- types.uint64,
227
- )
228
- signature_four_args = types.float32(
229
- types.CPointer(types.float32),
230
- types.uint64,
231
- types.CPointer(types.float32),
232
- types.uint64,
233
- )
234
-
235
- @cfunc(signature_two_args)
236
- def python_inner_product_two_args(a, b):
237
- a_array = carray(a, ndim)
238
- b_array = carray(b, ndim)
239
- c = 0.0
240
- for i in range(ndim):
241
- c += a_array[i] * b_array[i]
242
- return 1 - c
243
-
244
- @cfunc(signature_three_args)
245
- def python_inner_product_three_args(a, b, ndim):
246
- a_array = carray(a, ndim)
247
- b_array = carray(b, ndim)
248
- c = 0.0
249
- for i in range(ndim):
250
- c += a_array[i] * b_array[i]
251
- return 1 - c
252
-
253
- @cfunc(signature_four_args)
254
- def python_inner_product_four_args(a, a_ndim, b, b_ndim):
255
- a_array = carray(a, a_ndim)
256
- b_array = carray(b, b_ndim)
257
- c = 0.0
258
- for i in range(a_ndim):
259
- c += a_array[i] * b_array[i]
260
- return 1 - c
261
-
262
- functions = [
263
- python_inner_product_two_args,
264
- python_inner_product_three_args,
265
- python_inner_product_four_args,
266
- ]
267
- signatures = [
268
- MetricSignature.ArrayArray,
269
- MetricSignature.ArrayArraySize,
270
- MetricSignature.ArraySizeArraySize,
271
- ]
272
- for function, signature in zip(functions, signatures):
273
- metric = CompiledMetric(
274
- pointer=function.address,
275
- kind=MetricKind.IP,
276
- signature=signature,
277
- )
278
- index = Index(ndim=ndim, metric=metric)
279
-
280
- labels = np.arange(batch_size)
281
- vectors = random_vectors(count=batch_size, ndim=ndim)
282
-
283
- index.add(labels, vectors)
284
- matches, distances, count = index.search(vectors, 10)
285
- assert matches.shape[0] == distances.shape[0]
286
- assert count.shape[0] == batch_size
287
-
288
-
289
- @pytest.mark.parametrize("ndim", dimensions[-1:])
290
- @pytest.mark.parametrize("batch_size", batch_sizes[-1:])
291
- def test_index_cppyy(ndim: int, batch_size: int):
292
- try:
293
- import cppyy
294
- import cppyy.ll
295
- except ImportError:
296
- return
297
-
298
- cppyy.cppdef(
299
- """
300
- float inner_product_two_args(float *a, float *b) {
301
- float result = 0;
302
- #pragma unroll
303
- for (size_t i = 0; i != ndim; ++i)
304
- result += a[i] * b[i];
305
- return 1 - result;
306
- }
307
-
308
- float inner_product_three_args(float *a, float *b, size_t n) {
309
- float result = 0;
310
- for (size_t i = 0; i != n; ++i)
311
- result += a[i] * b[i];
312
- return 1 - result;
313
- }
314
-
315
- float inner_product_four_args(float *a, size_t an, float *b, size_t) {
316
- float result = 0;
317
- for (size_t i = 0; i != an; ++i)
318
- result += a[i] * b[i];
319
- return 1 - result;
320
- }
321
- """.replace(
322
- "ndim", str(ndim)
323
- )
324
- )
325
-
326
- functions = [
327
- cppyy.gbl.inner_product_two_args,
328
- cppyy.gbl.inner_product_three_args,
329
- cppyy.gbl.inner_product_four_args,
330
- ]
331
- signatures = [
332
- MetricSignature.ArrayArray,
333
- MetricSignature.ArrayArraySize,
334
- MetricSignature.ArraySizeArraySize,
335
- ]
336
- for function, signature in zip(functions, signatures):
337
- metric = CompiledMetric(
338
- pointer=cppyy.ll.addressof(function),
339
- kind=MetricKind.IP,
340
- signature=signature,
341
- )
342
- index = Index(ndim=ndim, metric=metric)
343
-
344
- labels = np.arange(batch_size)
345
- vectors = random_vectors(count=batch_size, ndim=ndim)
346
-
347
- index.add(labels, vectors)
348
- matches, distances, count = index.search(vectors, 10)
349
- assert matches.shape[0] == distances.shape[0]
350
- assert count.shape[0] == batch_size
351
-
352
-
353
- @pytest.mark.parametrize("ndim", [8])
354
- @pytest.mark.parametrize("batch_size", batch_sizes)
355
- def test_index_peachpy(ndim: int, batch_size: int):
356
- try:
357
- from peachpy import (
358
- Argument,
359
- ptr,
360
- float_,
361
- const_float_,
362
- )
363
- from peachpy.x86_64 import (
364
- abi,
365
- Function,
366
- uarch,
367
- isa,
368
- GeneralPurposeRegister64,
369
- LOAD,
370
- YMMRegister,
371
- VSUBPS,
372
- VADDPS,
373
- VHADDPS,
374
- VMOVUPS,
375
- VFMADD231PS,
376
- VPERM2F128,
377
- VXORPS,
378
- RETURN,
379
- )
380
- except ImportError:
381
- return
382
-
383
- a = Argument(ptr(const_float_), name="a")
384
- b = Argument(ptr(const_float_), name="b")
385
-
386
- with Function(
387
- "InnerProduct", (a, b), float_, target=uarch.default + isa.avx + isa.avx2
388
- ) as asm_function:
389
- # Request two 64-bit general-purpose registers for addresses
390
- reg_a, reg_b = GeneralPurposeRegister64(), GeneralPurposeRegister64()
391
- LOAD.ARGUMENT(reg_a, a)
392
- LOAD.ARGUMENT(reg_b, b)
393
-
394
- # Load the vectors
395
- ymm_a = YMMRegister()
396
- ymm_b = YMMRegister()
397
- VMOVUPS(ymm_a, [reg_a])
398
- VMOVUPS(ymm_b, [reg_b])
399
-
400
- # Prepare the accumulator
401
- ymm_c = YMMRegister()
402
- ymm_one = YMMRegister()
403
- VXORPS(ymm_c, ymm_c, ymm_c)
404
- VXORPS(ymm_one, ymm_one, ymm_one)
405
-
406
- # Accumulate A and B product into C
407
- VFMADD231PS(ymm_c, ymm_a, ymm_b)
408
-
409
- # Reduce the contents of a YMM register
410
- ymm_c_permuted = YMMRegister()
411
- VPERM2F128(ymm_c_permuted, ymm_c, ymm_c, 1)
412
- VADDPS(ymm_c, ymm_c, ymm_c_permuted)
413
- VHADDPS(ymm_c, ymm_c, ymm_c)
414
- VHADDPS(ymm_c, ymm_c, ymm_c)
415
-
416
- # Negate the values, to go from "similarity" to "distance"
417
- VSUBPS(ymm_c, ymm_one, ymm_c)
418
-
419
- # A common convention is to return floats in XMM registers
420
- RETURN(ymm_c.as_xmm)
421
-
422
- python_function = asm_function.finalize(abi.detect()).encode().load()
423
- metric = CompiledMetric(
424
- pointer=python_function.loader.code_address,
425
- kind=MetricKind.IP,
426
- signature=MetricSignature.ArrayArray,
427
- )
428
- index = Index(ndim=ndim, metric=metric)
429
-
430
- labels = np.arange(batch_size)
431
- vectors = random_vectors(count=batch_size, ndim=ndim)
432
-
433
- index.add(labels, vectors)
434
- matches, distances, count = index.search(vectors, 10)
435
- assert matches.shape[0] == distances.shape[0]
436
- assert count.shape[0] == batch_size
236
+ # Match entries aginst themselves
237
+ index_copy: Index = index.copy()
238
+ mapping: dict = index.join(index_copy, exact=True)
239
+ for man, woman in mapping.items():
240
+ assert man == woman, "Stable marriage failed"
437
241
 
438
242
 
439
243
  @pytest.mark.parametrize("bits", dimensions)
@@ -0,0 +1,244 @@
1
+ import pytest
2
+ import numpy as np
3
+
4
+ from usearch.eval import random_vectors
5
+
6
+ from usearch.index import (
7
+ Index,
8
+ MetricKind,
9
+ MetricSignature,
10
+ CompiledMetric,
11
+ )
12
+
13
+ dimensions = [3, 97, 256]
14
+ batch_sizes = [1, 77]
15
+
16
+
17
+ @pytest.mark.parametrize("ndim", dimensions)
18
+ @pytest.mark.parametrize("batch_size", batch_sizes)
19
+ def test_index_numba(ndim: int, batch_size: int):
20
+ try:
21
+ from numba import cfunc, types, carray
22
+ except ImportError:
23
+ return
24
+
25
+ # Showcases how to use Numba to JIT-compile similarity measures for USearch.
26
+ # https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#c-callbacks
27
+ signature_two_args = types.float32(
28
+ types.CPointer(types.float32),
29
+ types.CPointer(types.float32),
30
+ )
31
+ signature_three_args = types.float32(
32
+ types.CPointer(types.float32),
33
+ types.CPointer(types.float32),
34
+ types.uint64,
35
+ )
36
+ signature_four_args = types.float32(
37
+ types.CPointer(types.float32),
38
+ types.uint64,
39
+ types.CPointer(types.float32),
40
+ types.uint64,
41
+ )
42
+
43
+ @cfunc(signature_two_args)
44
+ def python_inner_product_two_args(a, b):
45
+ a_array = carray(a, ndim)
46
+ b_array = carray(b, ndim)
47
+ c = 0.0
48
+ for i in range(ndim):
49
+ c += a_array[i] * b_array[i]
50
+ return 1 - c
51
+
52
+ @cfunc(signature_three_args)
53
+ def python_inner_product_three_args(a, b, ndim):
54
+ a_array = carray(a, ndim)
55
+ b_array = carray(b, ndim)
56
+ c = 0.0
57
+ for i in range(ndim):
58
+ c += a_array[i] * b_array[i]
59
+ return 1 - c
60
+
61
+ @cfunc(signature_four_args)
62
+ def python_inner_product_four_args(a, a_ndim, b, b_ndim):
63
+ a_array = carray(a, a_ndim)
64
+ b_array = carray(b, b_ndim)
65
+ c = 0.0
66
+ for i in range(a_ndim):
67
+ c += a_array[i] * b_array[i]
68
+ return 1 - c
69
+
70
+ functions = [
71
+ python_inner_product_two_args,
72
+ python_inner_product_three_args,
73
+ python_inner_product_four_args,
74
+ ]
75
+ signatures = [
76
+ MetricSignature.ArrayArray,
77
+ MetricSignature.ArrayArraySize,
78
+ MetricSignature.ArraySizeArraySize,
79
+ ]
80
+ for function, signature in zip(functions, signatures):
81
+ metric = CompiledMetric(
82
+ pointer=function.address,
83
+ kind=MetricKind.IP,
84
+ signature=signature,
85
+ )
86
+ index = Index(ndim=ndim, metric=metric)
87
+
88
+ labels = np.arange(batch_size)
89
+ vectors = random_vectors(count=batch_size, ndim=ndim)
90
+
91
+ index.add(labels, vectors)
92
+ matches, distances, count = index.search(vectors, 10)
93
+ assert matches.shape[0] == distances.shape[0]
94
+ assert count.shape[0] == batch_size
95
+
96
+
97
+ @pytest.mark.parametrize("ndim", dimensions[-1:])
98
+ @pytest.mark.parametrize("batch_size", batch_sizes[-1:])
99
+ def test_index_cppyy(ndim: int, batch_size: int):
100
+ try:
101
+ import cppyy
102
+ import cppyy.ll
103
+ except ImportError:
104
+ return
105
+
106
+ cppyy.cppdef(
107
+ """
108
+ float inner_product_two_args(float *a, float *b) {
109
+ float result = 0;
110
+ #pragma unroll
111
+ for (size_t i = 0; i != ndim; ++i)
112
+ result += a[i] * b[i];
113
+ return 1 - result;
114
+ }
115
+
116
+ float inner_product_three_args(float *a, float *b, size_t n) {
117
+ float result = 0;
118
+ for (size_t i = 0; i != n; ++i)
119
+ result += a[i] * b[i];
120
+ return 1 - result;
121
+ }
122
+
123
+ float inner_product_four_args(float *a, size_t an, float *b, size_t) {
124
+ float result = 0;
125
+ for (size_t i = 0; i != an; ++i)
126
+ result += a[i] * b[i];
127
+ return 1 - result;
128
+ }
129
+ """.replace(
130
+ "ndim", str(ndim)
131
+ )
132
+ )
133
+
134
+ functions = [
135
+ cppyy.gbl.inner_product_two_args,
136
+ cppyy.gbl.inner_product_three_args,
137
+ cppyy.gbl.inner_product_four_args,
138
+ ]
139
+ signatures = [
140
+ MetricSignature.ArrayArray,
141
+ MetricSignature.ArrayArraySize,
142
+ MetricSignature.ArraySizeArraySize,
143
+ ]
144
+ for function, signature in zip(functions, signatures):
145
+ metric = CompiledMetric(
146
+ pointer=cppyy.ll.addressof(function),
147
+ kind=MetricKind.IP,
148
+ signature=signature,
149
+ )
150
+ index = Index(ndim=ndim, metric=metric)
151
+
152
+ labels = np.arange(batch_size)
153
+ vectors = random_vectors(count=batch_size, ndim=ndim)
154
+
155
+ index.add(labels, vectors)
156
+ matches, distances, count = index.search(vectors, 10)
157
+ assert matches.shape[0] == distances.shape[0]
158
+ assert count.shape[0] == batch_size
159
+
160
+
161
+ @pytest.mark.parametrize("ndim", [8])
162
+ @pytest.mark.parametrize("batch_size", batch_sizes)
163
+ def test_index_peachpy(ndim: int, batch_size: int):
164
+ try:
165
+ from peachpy import (
166
+ Argument,
167
+ ptr,
168
+ float_,
169
+ const_float_,
170
+ )
171
+ from peachpy.x86_64 import (
172
+ abi,
173
+ Function,
174
+ uarch,
175
+ isa,
176
+ GeneralPurposeRegister64,
177
+ LOAD,
178
+ YMMRegister,
179
+ VSUBPS,
180
+ VADDPS,
181
+ VHADDPS,
182
+ VMOVUPS,
183
+ VFMADD231PS,
184
+ VPERM2F128,
185
+ VXORPS,
186
+ RETURN,
187
+ )
188
+ except ImportError:
189
+ return
190
+
191
+ a = Argument(ptr(const_float_), name="a")
192
+ b = Argument(ptr(const_float_), name="b")
193
+
194
+ with Function(
195
+ "InnerProduct", (a, b), float_, target=uarch.default + isa.avx + isa.avx2
196
+ ) as asm_function:
197
+ # Request two 64-bit general-purpose registers for addresses
198
+ reg_a, reg_b = GeneralPurposeRegister64(), GeneralPurposeRegister64()
199
+ LOAD.ARGUMENT(reg_a, a)
200
+ LOAD.ARGUMENT(reg_b, b)
201
+
202
+ # Load the vectors
203
+ ymm_a = YMMRegister()
204
+ ymm_b = YMMRegister()
205
+ VMOVUPS(ymm_a, [reg_a])
206
+ VMOVUPS(ymm_b, [reg_b])
207
+
208
+ # Prepare the accumulator
209
+ ymm_c = YMMRegister()
210
+ ymm_one = YMMRegister()
211
+ VXORPS(ymm_c, ymm_c, ymm_c)
212
+ VXORPS(ymm_one, ymm_one, ymm_one)
213
+
214
+ # Accumulate A and B product into C
215
+ VFMADD231PS(ymm_c, ymm_a, ymm_b)
216
+
217
+ # Reduce the contents of a YMM register
218
+ ymm_c_permuted = YMMRegister()
219
+ VPERM2F128(ymm_c_permuted, ymm_c, ymm_c, 1)
220
+ VADDPS(ymm_c, ymm_c, ymm_c_permuted)
221
+ VHADDPS(ymm_c, ymm_c, ymm_c)
222
+ VHADDPS(ymm_c, ymm_c, ymm_c)
223
+
224
+ # Negate the values, to go from "similarity" to "distance"
225
+ VSUBPS(ymm_c, ymm_one, ymm_c)
226
+
227
+ # A common convention is to return floats in XMM registers
228
+ RETURN(ymm_c.as_xmm)
229
+
230
+ python_function = asm_function.finalize(abi.detect()).encode().load()
231
+ metric = CompiledMetric(
232
+ pointer=python_function.loader.code_address,
233
+ kind=MetricKind.IP,
234
+ signature=MetricSignature.ArrayArray,
235
+ )
236
+ index = Index(ndim=ndim, metric=metric)
237
+
238
+ labels = np.arange(batch_size)
239
+ vectors = random_vectors(count=batch_size, ndim=ndim)
240
+
241
+ index.add(labels, vectors)
242
+ matches, distances, count = index.search(vectors, 10)
243
+ assert matches.shape[0] == distances.shape[0]
244
+ assert count.shape[0] == batch_size
@@ -3,6 +3,7 @@ from time import time_ns
3
3
  from typing import Tuple, Any, Callable, Union, Optional, List
4
4
  from dataclasses import dataclass, asdict
5
5
  from collections import defaultdict
6
+ from math import ceil
6
7
 
7
8
  import numpy as np
8
9
 
@@ -47,7 +48,7 @@ def random_vectors(
47
48
  return x
48
49
 
49
50
 
50
- def recall_members(index: Index, *args, **kwargs) -> float:
51
+ def recall_members(index: Index, sample: float = 1, **kwargs) -> float:
51
52
  """Simplest benchmark for a quality of search, which queries every
52
53
  existing member of the index, to make sure approximate search finds
53
54
  the point itself.
@@ -59,9 +60,16 @@ def recall_members(index: Index, *args, **kwargs) -> float:
59
60
  """
60
61
  if len(index) == 0:
61
62
  return 0
63
+ if "k" not in kwargs:
64
+ kwargs["k"] = 1
62
65
 
63
- matches: Matches = index.search(index.vectors, 1, *args, **kwargs)
64
- return matches.recall_first(index.labels)
66
+ labels = index.labels
67
+ if sample != 1:
68
+ labels = np.random.choice(labels, int(ceil(len(labels) * sample)))
69
+
70
+ queries = index.get_vectors(labels, index.dtype)
71
+ matches: Matches = index.search(queries, **kwargs)
72
+ return matches.recall_first(labels)
65
73
 
66
74
 
67
75
  def measure_seconds(f: Callable) -> Tuple[float, Any]: