usearch 0.21.0 → 0.22.1

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.
@@ -319,15 +319,18 @@ class Index:
319
319
  pass `copy=False`, if you can guarantee the lifetime of the
320
320
  primary vectors store during the process of construction.
321
321
 
322
- :param labels: Unique identifier for passed vectors, optional
322
+ :param labels: Unique identifier(s) for passed vectors, optional
323
323
  :type labels: Buffer
324
- :param vectors: Collection of vectors.
324
+ :param vectors: Vector or a row-major matrix
325
325
  :type vectors: Buffer
326
326
  :param copy: Should the index store a copy of vectors, defaults to True
327
327
  :type copy: bool, optional
328
328
  :param threads: Optimal number of cores to use, defaults to 0
329
329
  :type threads: int, optional
330
-
330
+ :param log: Whether to print the progress bar, default to False
331
+ :type log: Union[str, bool], optional
332
+ :param batch_size: Number of vectors to process at once, defaults to 0
333
+ :type batch_size: int, optional
331
334
  :return: Inserted label or labels
332
335
  :type: Union[int, np.ndarray]
333
336
  """
@@ -399,17 +402,21 @@ class Index:
399
402
  log: Union[str, bool] = False,
400
403
  batch_size: int = 0,
401
404
  ) -> Matches:
402
- """Performs approximate nearest neighbors search for one or more queries.
405
+ """
406
+ Performs approximate nearest neighbors search for one or more queries.
403
407
 
404
408
  :param vectors: Query vector or vectors.
405
409
  :type vectors: Buffer
406
410
  :param k: Upper limit on the number of matches to find, defaults to 10
407
411
  :type k: int, optional
408
-
409
412
  :param threads: Optimal number of cores to use, defaults to 0
410
413
  :type threads: int, optional
411
414
  :param exact: Perform exhaustive linear-time exact search, defaults to False
412
415
  :type exact: bool, optional
416
+ :param log: Whether to print the progress bar, default to False
417
+ :type log: Union[str, bool], optional
418
+ :param batch_size: Number of vectors to process at once, defaults to 0
419
+ :type batch_size: int, optional
413
420
  :return: Approximate matches for one or more queries
414
421
  :rtype: Matches
415
422
  """
@@ -461,6 +468,34 @@ class Index:
461
468
  )
462
469
  return Matches(*tuple_)
463
470
 
471
+ def remove(
472
+ self,
473
+ labels: Union[int, Iterable[int]],
474
+ *,
475
+ compact: bool = False,
476
+ threads: int = 0,
477
+ ) -> Union[bool, int]:
478
+ """Removes one or move vectors from the index.
479
+
480
+ When working with extremely large indexes, you may want to
481
+ mark some entries deleted, instead of rebuilding a filtered index.
482
+ In other cases, rebuilding - is the recommended approach.
483
+
484
+ :param labels: Unique identifier for passed vectors, optional
485
+ :type labels: Buffer
486
+ :param compact: Removes links to removed nodes (expensive), defaults to False
487
+ :type compact: bool, optional
488
+ :param threads: Optimal number of cores to use, defaults to 0
489
+ :type threads: int, optional
490
+ :return: Number of removed entries
491
+ :type: Union[bool, int]
492
+ """
493
+ return self._compiled.remove(labels, compact=compact, threads=threads)
494
+
495
+ def rename(self, label_from: int, label_to: int) -> bool:
496
+ """Relabel existing entry"""
497
+ return self._compiled.rename(label_from, label_to)
498
+
464
499
  @property
465
500
  def specs(self) -> dict:
466
501
  return {
@@ -481,6 +516,19 @@ class Index:
481
516
  def __len__(self) -> int:
482
517
  return self._compiled.__len__()
483
518
 
519
+ def __delitem__(self, label: int) -> bool:
520
+ raise self.remove(label)
521
+
522
+ def __contains__(self, label: int) -> bool:
523
+ return self._compiled.__contains__(label)
524
+
525
+ def __getitem__(self, label: int) -> np.ndarray:
526
+ dtype = self.dtype
527
+ get_dtype = _to_numpy_compatible_dtype(dtype)
528
+ vector = self._compiled.__getitem__(label, get_dtype)
529
+ view_dtype = _to_numpy_dtype(dtype)
530
+ return None if vector is None else vector.view(view_dtype)
531
+
484
532
  @property
485
533
  def jit(self) -> bool:
486
534
  return self._metric_jit is not None
@@ -567,19 +615,50 @@ class Index:
567
615
  return result
568
616
 
569
617
  def join(self, other: Index, max_proposals: int = 0, exact: bool = False) -> dict:
618
+ """Performs "Semantic Join" or pairwise matching between `self` & `other` index.
619
+ Is different from `search`, as no collisions are allowed in resulting pairs.
620
+ Uses the concept of "Stable Marriages" from Combinatorics, famous for the 2012
621
+ Nobel Prize in Economics.
622
+
623
+ :param other: Another index.
624
+ :type other: Index
625
+ :param max_proposals: Limit on candidates evaluated per vector, defaults to 0
626
+ :type max_proposals: int, optional
627
+ :param exact: Controls if underlying `search` should be exact, defaults to False
628
+ :type exact: bool, optional
629
+ :return: Mapping from labels of `self` to labels of `other`
630
+ :rtype: dict
631
+ """
570
632
  return self._compiled.join(
571
633
  other=other._compiled,
572
634
  max_proposals=max_proposals,
573
635
  exact=exact,
574
636
  )
575
637
 
638
+ def get_labels(self, offset: int = 0, limit: int = 0) -> np.ndarray:
639
+ if limit == 0:
640
+ limit = 2**63 - 1
641
+ return self._compiled.get_labels(offset, limit)
642
+
576
643
  @property
577
644
  def labels(self) -> np.ndarray:
645
+ """Retrieves the labels of all vectors present in `self`
646
+
647
+ :return: Array of labels
648
+ :rtype: np.ndarray
649
+ """
578
650
  return self._compiled.labels
579
651
 
580
652
  def get_vectors(
581
- self, labels: np.ndarray, dtype: ScalarKind = ScalarKind.F32
653
+ self,
654
+ labels: np.ndarray,
655
+ dtype: ScalarKind = ScalarKind.F32,
582
656
  ) -> np.ndarray:
657
+ """Retrieves vectors associated with given `labels`
658
+
659
+ :return: Matrix of vectors (row-major)
660
+ :rtype: np.ndarray
661
+ """
583
662
  dtype = _normalize_dtype(dtype, self._metric_kind)
584
663
  get_dtype = _to_numpy_compatible_dtype(dtype)
585
664
  vectors = np.vstack([self._compiled.__getitem__(l, get_dtype) for l in labels])
@@ -590,22 +669,6 @@ class Index:
590
669
  def vectors(self) -> np.ndarray:
591
670
  return self.get_vectors(self.labels, self.dtype)
592
671
 
593
- def __delitem__(self, label: int):
594
- raise NotImplementedError()
595
-
596
- def __contains__(self, label: int) -> bool:
597
- return self._compiled.__contains__(label)
598
-
599
- def __getitem__(self, label: int) -> np.ndarray:
600
- dtype = self.dtype
601
- get_dtype = _to_numpy_compatible_dtype(dtype)
602
- vector = self._compiled.__getitem__(label, get_dtype)
603
- view_dtype = _to_numpy_dtype(dtype)
604
- return None if vector is None else vector.view(view_dtype)
605
-
606
- def remove(self, label: int):
607
- pass
608
-
609
672
  @property
610
673
  def max_level(self) -> int:
611
674
  return self._compiled.max_level
@@ -618,15 +681,24 @@ class Index:
618
681
  return self._compiled.level_stats(level)
619
682
 
620
683
  def __repr__(self) -> str:
621
- return f"usearch.index.Index({self.dtype} x {self.ndim}, {self.metric}, expansion: {self.expansion_add} & {self.expansion_search}, {len(self)} vectors across {self.max_level+1} levels)"
684
+ f = "usearch.Index({} x {}, {}, expansion: {} & {}, {} vectors in {} levels)"
685
+ return f.format(
686
+ self.dtype,
687
+ self.ndim,
688
+ self.metric,
689
+ self.expansion_add,
690
+ self.expansion_search,
691
+ len(self),
692
+ self.max_level + 1,
693
+ )
622
694
 
623
695
  def _repr_pretty_(self) -> str:
624
696
  level_stats = [
625
- f"--- {i}. {self.level_stats(i)} nodes" for i in range(self.max_level)
697
+ f"--- {i}. {self.level_stats(i).nodes} nodes" for i in range(self.max_level)
626
698
  ]
627
699
  return "\n".join(
628
700
  [
629
- "usearch.index.Index",
701
+ "usearch.Index",
630
702
  "- config" f"-- data type: {self.dtype}",
631
703
  f"-- dimensions: {self.ndim}",
632
704
  f"-- metric: {self.metric}",
package/rust/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # USearch for Rust
2
+
3
+ ## Installation
4
+
5
+ ```sh
6
+ cargo add usearch
7
+ ```
8
+
9
+ ## Quickstart
10
+
11
+ ```rust
12
+
13
+ let options = IndexOptions {
14
+ dimensions: 5,
15
+ metric: MetricKind::IP,
16
+ quantization: ScalarKind::F16,
17
+ connectivity: 0,
18
+ expansion_add: 0,
19
+ expansion_search: 0
20
+ };
21
+
22
+ let index = new_index(&options).unwrap();
23
+
24
+ assert!(index.reserve(10).is_ok());
25
+ assert!(index.capacity() >= 10);
26
+ assert!(index.connectivity() != 0);
27
+ assert_eq!(index.dimensions(), 3);
28
+ assert_eq!(index.size(), 0);
29
+
30
+ let first: [f32; 3] = [0.2, 0.1, 0.2];
31
+ let second: [f32; 3] = [0.2, 0.1, 0.2];
32
+
33
+ assert!(index.add(42, &first).is_ok());
34
+ assert!(index.add(43, &second).is_ok());
35
+ assert_eq!(index.size(), 2);
36
+
37
+ // Read back the tags
38
+ let results = index.search(&first, 10).unwrap();
39
+ assert_eq!(results.count, 2);
40
+ ```
41
+
42
+ ## Multi-Threading
43
+
44
+ ```rust
45
+ assert!(index.add_in_thread(42, &first, 0).is_ok());
46
+ assert!(index.add_in_thread(43, &second, 0).is_ok());
47
+ let results = index.search_in_thread(&first, 10, 0).unwrap();
48
+ ```
49
+
50
+ Being a systems-programming language, Rust has better control over memory management and concurrency but lacks function overloading.
51
+ Aside from the `add` and `search`, USearch Rust binding also provides `add_in_thread` and `search_in_thread`, which let users identify the calling thread to use underlying temporary memory more efficiently.
52
+
53
+ ## Serialization
54
+
55
+ ```rust
56
+ assert!(index.save("index.usearch").is_ok());
57
+ assert!(index.load("index.usearch").is_ok());
58
+ assert!(index.view("index.usearch").is_ok());
59
+ ```
60
+
61
+ ## Metrics
62
+
63
+ ```rust
64
+ assert!(new_l2sq(3, &quant, 0, 0, 0).is_ok());
65
+ assert!(new_cos(3, &quant, 0, 0, 0).is_ok());
66
+ assert!(new_haversine(&quant, 0, 0, 0).is_ok());
67
+ ```
@@ -0,0 +1,20 @@
1
+ # USearch for Swift
2
+
3
+ ## Installation
4
+
5
+ ```txt
6
+ https://github.com/unum-cloud/usearch
7
+ ```
8
+
9
+ ## Quickstart
10
+
11
+ ```swift
12
+ let index = Index.l2sq(dimensions: 3, connectivity: 8)
13
+ let vectorA: [Float32] = [0.3, 0.5, 1.2]
14
+ let vectorB: [Float32] = [0.4, 0.2, 1.2]
15
+ index.add(label: 42, vector: vectorA[...])
16
+ index.add(label: 43, vector: vectorB[...])
17
+
18
+ let results = index.search(vector: vectorA[...], count: 10)
19
+ assert(results.0[0] == 42)
20
+ ```
@@ -0,0 +1,15 @@
1
+ include(emscripten)
2
+
3
+ set(CMAKE_C_COMPILER emcc)
4
+ set(CMAKE_CXX_COMPILER em++)
5
+
6
+ set(USEARCH_PUNNED_INCLUDE_DIRS
7
+ "${CMAKE_CURRENT_SOURCE_DIR}/../c"
8
+ "${CMAKE_CURRENT_SOURCE_DIR}/../include"
9
+ "${CMAKE_CURRENT_SOURCE_DIR}/../fp16/include"
10
+ "${CMAKE_CURRENT_SOURCE_DIR}/../robin-map/include"
11
+ )
12
+
13
+ add_executable(index lib.cpp)
14
+ target_include_directories(index PRIVATE ${USEARCH_PUNNED_INCLUDE_DIRS})
15
+ set_target_properties(index PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/wasm)
package/wasm/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # USearch for WebAssembly
2
+
3
+ ## Installation
4
+
5
+ ```txt
6
+ https://github.com/unum-cloud/usearch
7
+ ```
8
+
9
+ ## Quickstart
10
+
11
+ ```wolfram
12
+
13
+ ```
package/wasm/lib.cpp ADDED
@@ -0,0 +1,5 @@
1
+ #define USEARCH_EXPORT EMSCRIPTEN_KEEPALIVE
2
+
3
+ #include <emscripten/emscripten.h>
4
+
5
+ #include <lib.cpp>
package/wasmer.toml ADDED
@@ -0,0 +1,8 @@
1
+ [package]
2
+ name="unum/usearch"
3
+ version="0.19.3"
4
+ description="Smaller & Faster Single-File Vector Search Engine from Unum"
5
+
6
+ [[module]]
7
+ name="index"
8
+ source="build/wasm/index.wasm"
@@ -0,0 +1,13 @@
1
+ # USearch for Wolfram
2
+
3
+ ## Installation
4
+
5
+ ```txt
6
+ https://github.com/unum-cloud/usearch
7
+ ```
8
+
9
+ ## Quickstart
10
+
11
+ ```wolfram
12
+
13
+ ```