usearch 0.18.3 → 0.18.5

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.
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "usearch"
3
- version = "0.18.3"
3
+ version = "0.18.5"
4
4
  authors = ["Ashot Vardanian <1983160+ashvardanian@users.noreply.github.com>"]
5
5
  description = "Smaller & Faster Single-File Vector Search Engine from Unum"
6
6
  edition = "2021"
package/conanfile.py CHANGED
@@ -4,7 +4,7 @@ from conans import ConanFile
4
4
  class USearchConan(ConanFile):
5
5
 
6
6
  name = 'USearch'
7
- version = '0.18.3'
7
+ version = '0.18.5'
8
8
  license = 'Apache 2.0'
9
9
  url = 'https://github.com/unum-cloud/usearch'
10
10
  description = 'Smaller & Faster Single-File Vector Search Engine from Unum'
@@ -269,7 +269,7 @@ template <typename scalar_at = float, typename result_at = scalar_at> struct cos
269
269
  ab += result_t(a[i]) * result_t(b[i]), //
270
270
  a2 += square<result_t>(a[i]), //
271
271
  b2 += square<result_t>(b[i]);
272
- return ab ? (1 - ab / (std::sqrt(a2) * std::sqrt(b2))) : 0;
272
+ return (ab != 0) ? (1 - ab / (std::sqrt(a2) * std::sqrt(b2))) : 0;
273
273
  }
274
274
  };
275
275
 
@@ -36,7 +36,7 @@ struct cos_f8_t {
36
36
  a2 += square(ai);
37
37
  b2 += square(bi);
38
38
  }
39
- return 1.f - ab / (std::sqrt(a2) * std::sqrt(b2));
39
+ return (ab != 0) ? (1.f - ab / (std::sqrt(a2) * std::sqrt(b2))) : 0;
40
40
  }
41
41
  };
42
42
 
@@ -328,7 +328,7 @@ class index_punned_dense_gt {
328
328
  std::size_t vector_bytes = dimensions_ * sizeof(scalar_at);
329
329
 
330
330
  byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * config.thread;
331
- bool casted = cast(vector_data, vector_bytes, casted_data);
331
+ bool casted = cast(vector_data, dimensions_, casted_data);
332
332
  if (casted)
333
333
  vector_data = casted_data, vector_bytes = casted_vector_bytes_, config.store_vector = true;
334
334
 
@@ -349,7 +349,7 @@ class index_punned_dense_gt {
349
349
  std::size_t vector_bytes = dimensions_ * sizeof(scalar_at);
350
350
 
351
351
  byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * config.thread;
352
- bool casted = cast(vector_data, vector_bytes, casted_data);
352
+ bool casted = cast(vector_data, dimensions_, casted_data);
353
353
  if (casted)
354
354
  vector_data = casted_data, vector_bytes = casted_vector_bytes_;
355
355
 
@@ -365,7 +365,7 @@ class index_punned_dense_gt {
365
365
  std::size_t vector_bytes = dimensions_ * sizeof(scalar_at);
366
366
 
367
367
  byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * config.thread;
368
- bool casted = cast(vector_data, vector_bytes, casted_data);
368
+ bool casted = cast(vector_data, dimensions_, casted_data);
369
369
  if (casted)
370
370
  vector_data = casted_data, vector_bytes = casted_vector_bytes_;
371
371
 
@@ -388,10 +388,10 @@ class index_punned_dense_gt {
388
388
  }
389
389
  member_citerator_t iterator = typed_->cbegin() + id;
390
390
  member_cref_t member = *iterator;
391
- byte_t const* casted_vector = reinterpret_cast<byte_t const*>(member.vector.data());
392
- bool casted = cast(casted_vector, casted_vector_bytes_, (byte_t*)reconstructed);
391
+ byte_t const* punned_vector = reinterpret_cast<byte_t const*>(member.vector.data());
392
+ bool casted = cast(punned_vector, dimensions_, (byte_t*)reconstructed);
393
393
  if (!casted)
394
- std::memcpy(reconstructed, casted_vector, casted_vector_bytes_);
394
+ std::memcpy(reconstructed, punned_vector, casted_vector_bytes_);
395
395
  return true;
396
396
  }
397
397
 
@@ -505,30 +505,27 @@ class index_punned_dense_gt {
505
505
  static metric_and_meta_t ip_metric_f32_(std::size_t dimensions) {
506
506
  (void)dimensions;
507
507
  #if USEARCH_USE_SIMSIMD
508
- #if defined(USEARCH_DEFINED_X86)
509
- if (dimensions % 4 == 0)
508
+ if (hardware_supports(isa_t::avx2_k) && dimensions % 4 == 0)
510
509
  return {
511
510
  pun_metric_<simsimd_f32_t>([=](simsimd_f32_t const* a, simsimd_f32_t const* b) {
512
511
  return 1.f - simsimd_dot_f32x4avx2(a, b, dimensions);
513
512
  }),
514
513
  isa_t::avx2_k,
515
514
  };
516
- #elif defined(USEARCH_DEFINED_ARM)
517
- if (supports_arm_sve())
515
+ if (hardware_supports(isa_t::sve_k))
518
516
  return {
519
517
  pun_metric_<simsimd_f32_t>([=](simsimd_f32_t const* a, simsimd_f32_t const* b) {
520
518
  return 1.f - simsimd_dot_f32sve(a, b, dimensions);
521
519
  }),
522
520
  isa_t::sve_k,
523
521
  };
524
- if (dimensions % 4 == 0)
522
+ if (hardware_supports(isa_t::neon_k) && dimensions % 4 == 0)
525
523
  return {
526
524
  pun_metric_<simsimd_f32_t>([=](simsimd_f32_t const* a, simsimd_f32_t const* b) {
527
525
  return 1.f - simsimd_dot_f32x4neon(a, b, dimensions);
528
526
  }),
529
527
  isa_t::neon_k,
530
528
  };
531
- #endif
532
529
  #endif
533
530
  return pun_metric_<f32_t>(ip_gt<f32_t>{});
534
531
  }
@@ -536,23 +533,20 @@ class index_punned_dense_gt {
536
533
  static metric_and_meta_t cos_metric_f16_(std::size_t dimensions) {
537
534
  (void)dimensions;
538
535
  #if USEARCH_USE_SIMSIMD
539
- #if defined(USEARCH_DEFINED_X86)
540
- if (dimensions % 16 == 0)
536
+ if (hardware_supports(isa_t::avx512_k) && dimensions % 16 == 0)
541
537
  return {
542
538
  pun_metric_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) {
543
539
  return 1.f - simsimd_cos_f16x16avx512(a, b, dimensions);
544
540
  }),
545
541
  isa_t::avx512_k,
546
542
  };
547
- #elif defined(USEARCH_DEFINED_ARM)
548
- if (dimensions % 4 == 0)
543
+ if (hardware_supports(isa_t::neon_k) && dimensions % 4 == 0)
549
544
  return {
550
545
  pun_metric_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) {
551
546
  return 1.f - simsimd_cos_f16x4neon(a, b, dimensions);
552
547
  }),
553
548
  isa_t::neon_k,
554
549
  };
555
- #endif
556
550
  #endif
557
551
  return pun_metric_<f16_t>(cos_gt<f16_t, f32_t>{});
558
552
  }
@@ -560,14 +554,12 @@ class index_punned_dense_gt {
560
554
  static metric_and_meta_t cos_metric_f8_(std::size_t dimensions) {
561
555
  (void)dimensions;
562
556
  #if USEARCH_USE_SIMSIMD
563
- #if defined(USEARCH_DEFINED_ARM)
564
- if (dimensions % 16 == 0)
557
+ if (hardware_supports(isa_t::neon_k) && dimensions % 16 == 0)
565
558
  return {
566
559
  pun_metric_<int8_t>(
567
560
  [=](int8_t const* a, int8_t const* b) { return 1.f - simsimd_cos_i8x16neon(a, b, dimensions); }),
568
561
  isa_t::neon_k,
569
562
  };
570
- #endif
571
563
  #endif
572
564
  return pun_metric_<f8_bits_t>(cos_f8_t{dimensions});
573
565
  }
@@ -76,13 +76,23 @@ inline char const* isa_name(isa_t isa) noexcept {
76
76
  }
77
77
  }
78
78
 
79
- inline bool supports_arm_sve() noexcept {
80
- #if defined(USEARCH_DEFINED_ARM)
81
- #if defined(USEARCH_DEFINED_LINUX)
79
+ inline bool hardware_supports(isa_t isa) noexcept {
80
+ #if defined(USEARCH_DEFINED_ARM) && defined(USEARCH_DEFINED_LINUX)
82
81
  unsigned long capabilities = getauxval(AT_HWCAP);
83
- if (capabilities & HWCAP_SVE)
84
- return true;
82
+ switch (isa) {
83
+ case isa_t::neon_k: return capabilities & HWCAP_NEON;
84
+ case isa_t::sve_k: return capabilities & HWCAP_SVE;
85
+ default: return false;
86
+ }
85
87
  #endif
88
+
89
+ #if defined(USEARCH_DEFINED_X86) && defined(USEARCH_DEFINED_GCC)
90
+ __builtin_cpu_init();
91
+ switch (isa) {
92
+ case isa_t::avx2_k: return __builtin_cpu_supports("avx2");
93
+ case isa_t::avx512_k: return __builtin_cpu_supports("avx512f");
94
+ default: return false;
95
+ }
86
96
  #endif
87
97
  return false;
88
98
  }
@@ -482,12 +492,11 @@ using memory_mapping_allocator_t = aligned_allocator_t;
482
492
  #endif
483
493
 
484
494
  template <typename from_scalar_at, typename to_scalar_at> struct cast_gt {
485
- inline bool operator()(byte_t const* input, std::size_t bytes_in_input, byte_t* output) const {
495
+ inline bool operator()(byte_t const* input, std::size_t dimensions, byte_t* output) const {
486
496
  from_scalar_at const* typed_input = reinterpret_cast<from_scalar_at const*>(input);
487
497
  to_scalar_at* typed_output = reinterpret_cast<to_scalar_at*>(output);
488
- std::size_t size_input = bytes_in_input / sizeof(from_scalar_at);
489
498
  auto converter = [](from_scalar_at from) { return to_scalar_at(from); };
490
- std::transform(typed_input, typed_input + size_input, typed_output, converter);
499
+ std::transform(typed_input, typed_input + dimensions, typed_output, converter);
491
500
  return true;
492
501
  }
493
502
  };
@@ -513,23 +522,21 @@ template <> struct cast_gt<b1x8_t, b1x8_t> {
513
522
  };
514
523
 
515
524
  template <typename from_scalar_at> struct cast_gt<from_scalar_at, b1x8_t> {
516
- inline bool operator()(byte_t const* input, std::size_t bytes_in_input, byte_t* output) const {
525
+ inline bool operator()(byte_t const* input, std::size_t dimensions, byte_t* output) const {
517
526
  from_scalar_at const* typed_input = reinterpret_cast<from_scalar_at const*>(input);
518
527
  unsigned char* typed_output = reinterpret_cast<unsigned char*>(output);
519
- std::size_t size_input = bytes_in_input / sizeof(from_scalar_at);
520
- for (std::size_t i = 0; i != size_input; ++i)
521
- typed_output[i / CHAR_BIT] |= bool(typed_input[i]) ? (1 << (i & (CHAR_BIT - 1))) : 0;
528
+ for (std::size_t i = 0; i != dimensions; ++i)
529
+ typed_output[i / CHAR_BIT] |= bool(typed_input[i]) ? (128 >> (i & (CHAR_BIT - 1))) : 0;
522
530
  return true;
523
531
  }
524
532
  };
525
533
 
526
534
  template <typename to_scalar_at> struct cast_gt<b1x8_t, to_scalar_at> {
527
- inline bool operator()(byte_t const* input, std::size_t bytes_in_input, byte_t* output) const {
535
+ inline bool operator()(byte_t const* input, std::size_t dimensions, byte_t* output) const {
528
536
  unsigned char const* typed_input = reinterpret_cast<unsigned char const*>(input);
529
537
  to_scalar_at* typed_output = reinterpret_cast<to_scalar_at*>(output);
530
- std::size_t size_input = bytes_in_input * CHAR_BIT;
531
- for (std::size_t i = 0; i != size_input; ++i)
532
- typed_output[i] = bool(typed_input[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1))));
538
+ for (std::size_t i = 0; i != dimensions; ++i)
539
+ typed_output[i] = bool(typed_input[i / CHAR_BIT] & (128 >> (i & (CHAR_BIT - 1))));
533
540
  return true;
534
541
  }
535
542
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "0.18.3",
3
+ "version": "0.18.5",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ashot Vardanian",
6
6
  "license": "Apache 2.0",
package/python/lib.cpp CHANGED
@@ -13,6 +13,7 @@
13
13
  * @copyright Copyright (c) 2023
14
14
  */
15
15
  #define PY_SSIZE_T_CLEAN
16
+ #define NOMINMAX // Some of our dependencies call `std::max(x, y)`, which crashes Windows builds
16
17
  #include <thread>
17
18
 
18
19
  #include <pybind11/numpy.h>
@@ -393,7 +394,8 @@ template <typename index_at> void view_index(index_at& index, std::string const&
393
394
 
394
395
  template <typename internal_at, typename external_at = internal_at, typename index_at = void>
395
396
  py::object get_typed_member(index_at const& index, label_t label) {
396
- py::array_t<external_at> result_py(static_cast<Py_ssize_t>(index.scalar_words()));
397
+ std::size_t result_slots = std::is_same<internal_at, b1x8_t>() ? index.scalar_words() : index.dimensions();
398
+ py::array_t<external_at> result_py(static_cast<Py_ssize_t>(result_slots));
397
399
  auto result_py1d = result_py.template mutable_unchecked<1>();
398
400
  if (!index.get(label, (internal_at*)&result_py1d(0)))
399
401
  return py::none();
@@ -4,7 +4,6 @@ import itertools
4
4
  from typing import List, Optional, Iterable
5
5
  from dataclasses import asdict
6
6
 
7
- import fire
8
7
  import numpy as np
9
8
  import pandas as pd
10
9
 
@@ -5,7 +5,7 @@ import numpy as np
5
5
  from usearch.io import load_matrix, save_matrix
6
6
  from usearch.eval import recall_members, random_vectors
7
7
 
8
- from usearch.index import Index, SparseIndex, MetricKind, ScalarKind, Matches
8
+ from usearch.index import Index, SparseIndex, MetricKind, ScalarKind, Matches, MetricKindBitwise
9
9
  from usearch.index import _normalize_dtype
10
10
  from usearch.index import (
11
11
  DEFAULT_CONNECTIVITY,
@@ -181,10 +181,13 @@ def test_bitwise_index(bits: int, metric: MetricKind, connectivity: int, batch_s
181
181
  index = Index(ndim=bits, metric=metric, connectivity=connectivity)
182
182
 
183
183
  labels = np.arange(batch_size)
184
- bit_vectors = np.random.randint(2, size=(batch_size, bits))
185
- bit_vectors = np.packbits(bit_vectors, axis=1)
184
+ byte_vectors = np.random.randint(2, size=(batch_size, bits))
185
+ bit_vectors = np.packbits(byte_vectors, axis=1)
186
186
 
187
187
  index.add(labels, bit_vectors)
188
+ assert np.allclose(index.get_vectors(labels), byte_vectors, atol=0.1)
189
+ assert np.alltrue(index.get_vectors(labels, ScalarKind.B1) == bit_vectors)
190
+
188
191
  index.search(bit_vectors, 10)
189
192
 
190
193
 
package/setup.py CHANGED
@@ -9,7 +9,7 @@ compile_args = ['-std=c++11', '-O3']
9
9
  link_args = []
10
10
  macros_args = [
11
11
  ('USEARCH_USE_NATIVE_F16', '0'),
12
- ('USEARCH_USE_SIMSIMD', '1'),
12
+ ('USEARCH_USE_SIMSIMD', '0'),
13
13
  ]
14
14
 
15
15
  if sys.platform == 'linux':