usearch 2.3.2 → 2.4.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.
@@ -649,7 +649,9 @@ class aligned_allocator_gt {
649
649
  using size_type = std::size_t;
650
650
  using pointer = element_at*;
651
651
  using const_pointer = element_at const*;
652
- template <typename other_element_at> struct rebind { using other = aligned_allocator_gt<other_element_at>; };
652
+ template <typename other_element_at> struct rebind {
653
+ using other = aligned_allocator_gt<other_element_at>;
654
+ };
653
655
 
654
656
  constexpr std::size_t alignment() const { return alignment_ak; }
655
657
 
@@ -1315,17 +1317,16 @@ template <typename scalar_at = float, typename result_at = scalar_at> struct met
1315
1317
  result_t lat_a = a[0], lon_a = a[1];
1316
1318
  result_t lat_b = b[0], lon_b = b[1];
1317
1319
 
1318
- result_t lat_delta = angle_to_radians<result_t>(lat_b - lat_a);
1319
- result_t lon_delta = angle_to_radians<result_t>(lon_b - lon_a);
1320
+ result_t lat_delta = angle_to_radians<result_t>(lat_b - lat_a) / 2;
1321
+ result_t lon_delta = angle_to_radians<result_t>(lon_b - lon_a) / 2;
1320
1322
 
1321
1323
  result_t converted_lat_a = angle_to_radians<result_t>(lat_a);
1322
1324
  result_t converted_lat_b = angle_to_radians<result_t>(lat_b);
1323
1325
 
1324
- result_t x = //
1325
- square(std::sin(lat_delta / 2.f)) +
1326
- std::cos(converted_lat_a) * std::cos(converted_lat_b) * square(std::sin(lon_delta / 2.f));
1326
+ result_t x = square(std::sin(lat_delta)) + //
1327
+ std::cos(converted_lat_a) * std::cos(converted_lat_b) * square(std::sin(lon_delta));
1327
1328
 
1328
- return std::atan2(std::sqrt(x), std::sqrt(1.f - x));
1329
+ return 2 * std::asin(std::sqrt(x));
1329
1330
  }
1330
1331
  };
1331
1332
 
@@ -1556,5 +1557,513 @@ class metric_punned_t {
1556
1557
  // clang-format on
1557
1558
  };
1558
1559
 
1560
+ /**
1561
+ * @brief C++11 Multi-Hash-Set with Linear Probing.
1562
+ *
1563
+ * - Allows multiple equivalent values,
1564
+ * - Supports transparent hashing and equality operator.
1565
+ * - Doesn't throw exceptions, if forbidden.
1566
+ * - Doesn't need reserving a value for deletions.
1567
+ *
1568
+ * @section Layout
1569
+ *
1570
+ * For every slot we store 2 extra bits for 3 possible states: empty, populated, or deleted.
1571
+ */
1572
+ template <typename element_at, typename hash_at, typename equals_at, typename allocator_at = std::allocator<char>>
1573
+ class flat_hash_multi_set_gt {
1574
+ public:
1575
+ using element_t = element_at;
1576
+ using hash_t = hash_at;
1577
+ using equals_t = equals_at;
1578
+ using allocator_t = allocator_at;
1579
+
1580
+ static constexpr std::size_t slots_per_bucket() { return 64; }
1581
+ static constexpr std::size_t bytes_per_bucket() {
1582
+ return slots_per_bucket() * sizeof(element_t) + sizeof(bucket_header_t);
1583
+ }
1584
+
1585
+ private:
1586
+ struct bucket_header_t {
1587
+ std::uint64_t populated{};
1588
+ std::uint64_t deleted{};
1589
+ };
1590
+ char* data_ = nullptr;
1591
+ std::size_t buckets_ = 0;
1592
+ std::size_t populated_slots_ = 0;
1593
+ /// @brief Number of slots
1594
+ std::size_t capacity_slots_ = 0;
1595
+
1596
+ struct slot_ref_t {
1597
+ bucket_header_t& header;
1598
+ std::uint64_t mask;
1599
+ element_t& element;
1600
+ };
1601
+
1602
+ slot_ref_t slot_ref(char* data, std::size_t slot_index) const noexcept {
1603
+ std::size_t bucket_index = slot_index / slots_per_bucket();
1604
+ std::size_t in_bucket_index = slot_index % slots_per_bucket();
1605
+ auto bucket_pointer = data + bytes_per_bucket() * bucket_index;
1606
+ auto slot_pointer = bucket_pointer + sizeof(bucket_header_t) + sizeof(element_t) * in_bucket_index;
1607
+ return {
1608
+ *reinterpret_cast<bucket_header_t*>(bucket_pointer),
1609
+ std::uint64_t(1ull) << in_bucket_index,
1610
+ *reinterpret_cast<element_t*>(slot_pointer),
1611
+ };
1612
+ }
1613
+
1614
+ slot_ref_t slot_ref(std::size_t slot_index) const noexcept { return slot_ref(data_, slot_index); }
1615
+
1616
+ bool populate_slot(slot_ref_t slot, element_t const& new_element) {
1617
+ if (slot.header.populated & slot.mask) {
1618
+ slot.element = new_element;
1619
+ slot.header.deleted &= ~slot.mask;
1620
+ return false;
1621
+ } else {
1622
+ new (&slot.element) element_t(new_element);
1623
+ slot.header.populated |= slot.mask;
1624
+ return true;
1625
+ }
1626
+ }
1627
+
1628
+ public:
1629
+ std::size_t size() const noexcept { return populated_slots_; }
1630
+ std::size_t capacity() const noexcept { return capacity_slots_; }
1631
+
1632
+ flat_hash_multi_set_gt() noexcept {}
1633
+ ~flat_hash_multi_set_gt() noexcept { reset(); }
1634
+
1635
+ flat_hash_multi_set_gt(flat_hash_multi_set_gt const& other) {
1636
+
1637
+ // On Windows allocating a zero-size array would fail
1638
+ if (!other.buckets_) {
1639
+ reset();
1640
+ return;
1641
+ }
1642
+
1643
+ // Allocate new memory
1644
+ data_ = (char*)allocator_t{}.allocate(other.buckets_ * bytes_per_bucket());
1645
+ if (!data_)
1646
+ throw std::bad_alloc();
1647
+
1648
+ // Copy metadata
1649
+ buckets_ = other.buckets_;
1650
+ populated_slots_ = other.populated_slots_;
1651
+ capacity_slots_ = other.capacity_slots_;
1652
+
1653
+ // Initialize new buckets to empty
1654
+ std::memset(data_, 0, buckets_ * bytes_per_bucket());
1655
+
1656
+ // Copy elements and bucket headers
1657
+ for (std::size_t i = 0; i < capacity_slots_; ++i) {
1658
+ slot_ref_t old_slot = other.slot_ref(i);
1659
+ if ((old_slot.header.populated & old_slot.mask) && !(old_slot.header.deleted & old_slot.mask)) {
1660
+ slot_ref_t new_slot = slot_ref(i);
1661
+ populate_slot(new_slot, old_slot.element);
1662
+ }
1663
+ }
1664
+ }
1665
+
1666
+ flat_hash_multi_set_gt& operator=(flat_hash_multi_set_gt const& other) {
1667
+
1668
+ // On Windows allocating a zero-size array would fail
1669
+ if (!other.buckets_) {
1670
+ reset();
1671
+ return *this;
1672
+ }
1673
+
1674
+ // Handle self-assignment
1675
+ if (this == &other)
1676
+ return *this;
1677
+
1678
+ // Clear existing data
1679
+ clear();
1680
+ if (data_)
1681
+ allocator_t{}.deallocate(data_, buckets_ * bytes_per_bucket());
1682
+
1683
+ // Allocate new memory
1684
+ data_ = (char*)allocator_t{}.allocate(other.buckets_ * bytes_per_bucket());
1685
+ if (!data_)
1686
+ throw std::bad_alloc();
1687
+
1688
+ // Copy metadata
1689
+ buckets_ = other.buckets_;
1690
+ populated_slots_ = other.populated_slots_;
1691
+ capacity_slots_ = other.capacity_slots_;
1692
+
1693
+ // Initialize new buckets to empty
1694
+ std::memset(data_, 0, buckets_ * bytes_per_bucket());
1695
+
1696
+ // Copy elements and bucket headers
1697
+ for (std::size_t i = 0; i < capacity_slots_; ++i) {
1698
+ slot_ref_t old_slot = other.slot_ref(i);
1699
+ if ((old_slot.header.populated & old_slot.mask) && !(old_slot.header.deleted & old_slot.mask)) {
1700
+ slot_ref_t new_slot = slot_ref(i);
1701
+ populate_slot(new_slot, old_slot.element);
1702
+ }
1703
+ }
1704
+
1705
+ return *this;
1706
+ }
1707
+
1708
+ void clear() noexcept {
1709
+ // Call the destructors
1710
+ for (std::size_t i = 0; i < capacity_slots_; ++i) {
1711
+ slot_ref_t slot = slot_ref(i);
1712
+ if ((slot.header.populated & slot.mask) & (~slot.header.deleted & slot.mask))
1713
+ slot.element.~element_t();
1714
+ }
1715
+
1716
+ // Reset populated slots count
1717
+ std::memset(data_, 0, buckets_ * bytes_per_bucket());
1718
+ populated_slots_ = 0;
1719
+ }
1720
+
1721
+ void reset() noexcept {
1722
+ clear(); // Clear all elements
1723
+ if (data_)
1724
+ allocator_t{}.deallocate(data_, buckets_ * bytes_per_bucket());
1725
+ buckets_ = 0;
1726
+ populated_slots_ = 0;
1727
+ capacity_slots_ = 0;
1728
+ }
1729
+
1730
+ bool try_reserve(std::size_t capacity) noexcept {
1731
+ if (capacity * 3u <= capacity_slots_ * 2u)
1732
+ return true;
1733
+
1734
+ // Calculate new sizes
1735
+ std::size_t new_slots = ceil2((capacity * 3ul) / 2ul);
1736
+ std::size_t new_buckets = divide_round_up<slots_per_bucket()>(new_slots);
1737
+ new_slots = new_buckets * slots_per_bucket(); // This must be a power of two!
1738
+ std::size_t new_bytes = new_buckets * bytes_per_bucket();
1739
+
1740
+ // Allocate new memory
1741
+ char* new_data = (char*)allocator_t{}.allocate(new_bytes);
1742
+ if (!new_data)
1743
+ return false;
1744
+
1745
+ // Initialize new buckets to empty
1746
+ std::memset(new_data, 0, new_bytes);
1747
+
1748
+ // Rehash and copy existing elements to new_data
1749
+ hash_t hasher;
1750
+ for (std::size_t i = 0; i < capacity_slots_; ++i) {
1751
+ slot_ref_t old_slot = slot_ref(i);
1752
+ if ((~old_slot.header.populated & old_slot.mask) | (old_slot.header.deleted & old_slot.mask))
1753
+ continue;
1754
+
1755
+ // Rehash
1756
+ std::size_t hash_value = hasher(old_slot.element);
1757
+ std::size_t new_slot_index = hash_value & (new_slots - 1);
1758
+
1759
+ // Linear probing to find an empty slot in new_data
1760
+ while (true) {
1761
+ slot_ref_t new_slot = slot_ref(new_data, new_slot_index);
1762
+ if (!(new_slot.header.populated & new_slot.mask) || (new_slot.header.deleted & new_slot.mask)) {
1763
+ populate_slot(new_slot, std::move(old_slot.element));
1764
+ new_slot.header.populated |= new_slot.mask;
1765
+ break;
1766
+ }
1767
+ new_slot_index = (new_slot_index + 1) & (new_slots - 1);
1768
+ }
1769
+ }
1770
+
1771
+ // Deallocate old data and update pointers and sizes
1772
+ if (data_)
1773
+ allocator_t{}.deallocate(data_, buckets_ * bytes_per_bucket());
1774
+ data_ = new_data;
1775
+ buckets_ = new_buckets;
1776
+ capacity_slots_ = new_slots;
1777
+
1778
+ return true;
1779
+ }
1780
+
1781
+ template <typename query_at> class equal_iterator_gt {
1782
+ public:
1783
+ using iterator_category = std::forward_iterator_tag;
1784
+ using value_type = element_t;
1785
+ using difference_type = std::ptrdiff_t;
1786
+ using pointer = element_t*;
1787
+ using reference = element_t&;
1788
+
1789
+ equal_iterator_gt(std::size_t index, flat_hash_multi_set_gt* parent, const query_at& query,
1790
+ const equals_t& equals)
1791
+ : index_(index), parent_(parent), query_(query), equals_(equals) {}
1792
+
1793
+ // Pre-increment
1794
+ equal_iterator_gt& operator++() {
1795
+ do {
1796
+ ++index_;
1797
+ if (index_ >= parent_->capacity_slots_) {
1798
+ break;
1799
+ }
1800
+ } while (!equals_(parent_->slot_ref(index_).element, query_) ||
1801
+ !(parent_->slot_ref(index_).header.populated & parent_->slot_ref(index_).mask));
1802
+ return *this;
1803
+ }
1804
+
1805
+ equal_iterator_gt operator++(int) {
1806
+ equal_iterator_gt temp = *this;
1807
+ ++(*this);
1808
+ return temp;
1809
+ }
1810
+
1811
+ reference operator*() { return parent_->slot_ref(index_).element; }
1812
+ pointer operator->() { return &parent_->slot_ref(index_).element; }
1813
+ bool operator!=(equal_iterator_gt const& other) const { return !(*this == other); }
1814
+ bool operator==(equal_iterator_gt const& other) const {
1815
+ return index_ == other.index_ && parent_ == other.parent_;
1816
+ }
1817
+
1818
+ private:
1819
+ std::size_t index_;
1820
+ flat_hash_multi_set_gt* parent_;
1821
+ query_at query_; // Store the query object
1822
+ equals_t equals_; // Store the equals functor
1823
+ };
1824
+
1825
+ template <typename query_at>
1826
+ std::pair<equal_iterator_gt<query_at>, equal_iterator_gt<query_at>>
1827
+ equal_range(query_at const& query) const noexcept {
1828
+
1829
+ equals_t equals;
1830
+ auto this_ptr = const_cast<flat_hash_multi_set_gt*>(this);
1831
+ auto end = equal_iterator_gt<query_at>(capacity_slots_, this_ptr, query, equals);
1832
+ if (!capacity_slots_)
1833
+ return {end, end};
1834
+
1835
+ hash_t hasher;
1836
+ std::size_t hash_value = hasher(query);
1837
+ std::size_t slot_index = hash_value & (capacity_slots_ - 1);
1838
+ std::size_t const start_index = slot_index;
1839
+ std::size_t first_equal_index = capacity_slots_;
1840
+
1841
+ // Linear probing to find the first equal element
1842
+ do {
1843
+ slot_ref_t slot = slot_ref(slot_index);
1844
+ if (slot.header.populated & ~slot.header.deleted & slot.mask) {
1845
+ if (equals(slot.element, query)) {
1846
+ first_equal_index = slot_index;
1847
+ break;
1848
+ }
1849
+ }
1850
+ // Stop if we find an empty slot
1851
+ else if (~slot.header.populated & slot.mask)
1852
+ break;
1853
+
1854
+ // Move to the next slot
1855
+ slot_index = (slot_index + 1) & (capacity_slots_ - 1);
1856
+ } while (slot_index != start_index);
1857
+
1858
+ return {equal_iterator_gt<query_at>(first_equal_index, this_ptr, query, equals), end};
1859
+ }
1860
+
1861
+ template <typename similar_at> bool pop_first(similar_at&& query, element_t& popped_value) noexcept {
1862
+
1863
+ if (!capacity_slots_)
1864
+ return false;
1865
+
1866
+ hash_t hasher;
1867
+ equals_t equals;
1868
+ std::size_t hash_value = hasher(query);
1869
+ std::size_t slot_index = hash_value & (capacity_slots_ - 1); // Assuming capacity_slots_ is a power of 2
1870
+ std::size_t start_index = slot_index; // To detect loop in probing
1871
+
1872
+ // Linear probing to find the first match
1873
+ do {
1874
+ slot_ref_t slot = slot_ref(slot_index);
1875
+ if (slot.header.populated & slot.mask) {
1876
+ if ((~slot.header.deleted & slot.mask) && equals(slot.element, query)) {
1877
+ // Found a match, mark as deleted
1878
+ slot.header.deleted |= slot.mask;
1879
+ --populated_slots_;
1880
+ popped_value = slot.element;
1881
+ return true; // Successfully removed
1882
+ }
1883
+ } else {
1884
+ // Stop if we find an empty slot
1885
+ break;
1886
+ }
1887
+
1888
+ // Move to the next slot
1889
+ slot_index = (slot_index + 1) & (capacity_slots_ - 1); // Assuming capacity_slots_ is a power of 2
1890
+ } while (slot_index != start_index);
1891
+
1892
+ return false; // No match found
1893
+ }
1894
+
1895
+ template <typename similar_at> std::size_t erase(similar_at&& query) noexcept {
1896
+
1897
+ if (!capacity_slots_)
1898
+ return 0;
1899
+
1900
+ hash_t hasher;
1901
+ equals_t equals;
1902
+ std::size_t hash_value = hasher(query);
1903
+ std::size_t slot_index = hash_value & (capacity_slots_ - 1); // Assuming capacity_slots_ is a power of 2
1904
+ std::size_t start_index = slot_index; // To detect loop in probing
1905
+ std::size_t count = 0; // Count of elements removed
1906
+
1907
+ // Linear probing to find all matches
1908
+ do {
1909
+ slot_ref_t slot = slot_ref(slot_index);
1910
+ if (slot.header.populated & slot.mask) {
1911
+ if ((~slot.header.deleted & slot.mask) && equals(slot.element, query)) {
1912
+ // Found a match, mark as deleted
1913
+ slot.header.deleted |= slot.mask;
1914
+ --populated_slots_;
1915
+ ++count; // Increment count of elements removed
1916
+ }
1917
+ } else {
1918
+ // Stop if we find an empty slot
1919
+ break;
1920
+ }
1921
+
1922
+ // Move to the next slot
1923
+ slot_index = (slot_index + 1) & (capacity_slots_ - 1); // Assuming capacity_slots_ is a power of 2
1924
+ } while (slot_index != start_index);
1925
+
1926
+ return count; // Return the number of elements removed
1927
+ }
1928
+
1929
+ template <typename similar_at> element_t const* find(similar_at&& query) const noexcept {
1930
+
1931
+ if (!capacity_slots_)
1932
+ return nullptr;
1933
+
1934
+ hash_t hasher;
1935
+ equals_t equals;
1936
+ std::size_t hash_value = hasher(query);
1937
+ std::size_t slot_index = hash_value & (capacity_slots_ - 1); // Assuming capacity_slots_ is a power of 2
1938
+ std::size_t start_index = slot_index; // To detect loop in probing
1939
+
1940
+ // Linear probing to find the first match
1941
+ do {
1942
+ slot_ref_t slot = slot_ref(slot_index);
1943
+ if (slot.header.populated & slot.mask) {
1944
+ if ((~slot.header.deleted & slot.mask) && equals(slot.element, query))
1945
+ return &slot.element; // Found a match, return pointer to the element
1946
+ } else {
1947
+ // Stop if we find an empty slot
1948
+ break;
1949
+ }
1950
+
1951
+ // Move to the next slot
1952
+ slot_index = (slot_index + 1) & (capacity_slots_ - 1); // Assuming capacity_slots_ is a power of 2
1953
+ } while (slot_index != start_index);
1954
+
1955
+ return nullptr; // No match found
1956
+ }
1957
+
1958
+ element_t const* end() const noexcept { return nullptr; }
1959
+
1960
+ template <typename func_at> void for_each(func_at&& func) const {
1961
+ for (std::size_t bucket_index = 0; bucket_index < buckets_; ++bucket_index) {
1962
+ auto bucket_pointer = data_ + bytes_per_bucket() * bucket_index;
1963
+ bucket_header_t& header = *reinterpret_cast<bucket_header_t*>(bucket_pointer);
1964
+ std::uint64_t populated = header.populated;
1965
+ std::uint64_t deleted = header.deleted;
1966
+
1967
+ // Iterate through slots in the bucket
1968
+ for (std::size_t in_bucket_index = 0; in_bucket_index < slots_per_bucket(); ++in_bucket_index) {
1969
+ std::uint64_t mask = std::uint64_t(1ull) << in_bucket_index;
1970
+
1971
+ // Check if the slot is populated and not deleted
1972
+ if ((populated & ~deleted) & mask) {
1973
+ auto slot_pointer = bucket_pointer + sizeof(bucket_header_t) + sizeof(element_t) * in_bucket_index;
1974
+ element_t const& element = *reinterpret_cast<element_t const*>(slot_pointer);
1975
+ func(element);
1976
+ }
1977
+ }
1978
+ }
1979
+ }
1980
+
1981
+ template <typename similar_at> std::size_t count(similar_at&& query) const noexcept {
1982
+
1983
+ if (!capacity_slots_)
1984
+ return 0;
1985
+
1986
+ hash_t hasher;
1987
+ equals_t equals;
1988
+ std::size_t hash_value = hasher(query);
1989
+ std::size_t slot_index = hash_value & (capacity_slots_ - 1);
1990
+ std::size_t start_index = slot_index; // To detect loop in probing
1991
+ std::size_t count = 0;
1992
+
1993
+ // Linear probing to find the range
1994
+ do {
1995
+ slot_ref_t slot = slot_ref(slot_index);
1996
+ if ((slot.header.populated & slot.mask) && (~slot.header.deleted & slot.mask)) {
1997
+ if (equals(slot.element, query))
1998
+ ++count;
1999
+ } else if (~slot.header.populated & slot.mask) {
2000
+ // Stop if we find an empty slot
2001
+ break;
2002
+ }
2003
+
2004
+ // Move to the next slot
2005
+ slot_index = (slot_index + 1) & (capacity_slots_ - 1);
2006
+ } while (slot_index != start_index);
2007
+
2008
+ return count;
2009
+ }
2010
+
2011
+ template <typename similar_at> bool contains(similar_at&& query) const noexcept {
2012
+
2013
+ if (!capacity_slots_)
2014
+ return false;
2015
+
2016
+ hash_t hasher;
2017
+ equals_t equals;
2018
+ std::size_t hash_value = hasher(query);
2019
+ std::size_t slot_index = hash_value & (capacity_slots_ - 1);
2020
+ std::size_t start_index = slot_index; // To detect loop in probing
2021
+
2022
+ // Linear probing to find the first match
2023
+ do {
2024
+ slot_ref_t slot = slot_ref(slot_index);
2025
+ if (slot.header.populated & slot.mask) {
2026
+ if ((~slot.header.deleted & slot.mask) && equals(slot.element, query))
2027
+ return true; // Found a match, exit early
2028
+ } else
2029
+ // Stop if we find an empty slot
2030
+ break;
2031
+
2032
+ // Move to the next slot
2033
+ slot_index = (slot_index + 1) & (capacity_slots_ - 1);
2034
+ } while (slot_index != start_index);
2035
+
2036
+ return false; // No match found
2037
+ }
2038
+
2039
+ void reserve(std::size_t capacity) {
2040
+ if (!try_reserve(capacity))
2041
+ throw std::bad_alloc();
2042
+ }
2043
+
2044
+ bool try_emplace(element_t const& element) noexcept {
2045
+ // Check if we need to resize
2046
+ if (populated_slots_ * 3u >= capacity_slots_ * 2u)
2047
+ if (!try_reserve(populated_slots_ + 1))
2048
+ return false;
2049
+
2050
+ hash_t hasher;
2051
+ std::size_t hash_value = hasher(element);
2052
+ std::size_t slot_index = hash_value & (capacity_slots_ - 1);
2053
+
2054
+ // Linear probing
2055
+ while (true) {
2056
+ slot_ref_t slot = slot_ref(slot_index);
2057
+ if ((~slot.header.populated & slot.mask) | (slot.header.deleted & slot.mask)) {
2058
+ // Found an empty or deleted slot
2059
+ populated_slots_ += populate_slot(slot, element);
2060
+ return true;
2061
+ }
2062
+ // Move to the next slot
2063
+ slot_index = (slot_index + 1) & (capacity_slots_ - 1);
2064
+ }
2065
+ }
2066
+ };
2067
+
1559
2068
  } // namespace usearch
1560
2069
  } // namespace unum
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "2.3.2",
3
+ "version": "2.4.1",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ash Vardanian",
6
6
  "license": "Apache 2.0",
@@ -25,7 +25,7 @@
25
25
  "@semantic-release/exec": "^6.0.3",
26
26
  "@semantic-release/git": "^10.0.1",
27
27
  "conventional-changelog-eslint": "^3.0.9",
28
- "semantic-release": "^21.1.1",
28
+ "semantic-release": "^21.1.2",
29
29
  "typescript": "^5.1.6"
30
30
  }
31
31
  }
package/python/lib.cpp CHANGED
@@ -177,10 +177,12 @@ static dense_index_py_t make_index( //
177
177
  metric_kind_t metric_kind, //
178
178
  metric_signature_t metric_signature, //
179
179
  std::uintptr_t metric_uintptr, //
180
- bool multi) {
180
+ bool multi, //
181
+ bool enable_key_lookups) {
181
182
 
182
183
  index_dense_config_t config(connectivity, expansion_add, expansion_search);
183
184
  config.multi = multi;
185
+ config.enable_key_lookups = enable_key_lookups;
184
186
 
185
187
  metric_t metric = //
186
188
  metric_uintptr //
@@ -606,9 +608,9 @@ static py::tuple search_many_brute_force( //
606
608
  if (dataset_kind != queries_kind)
607
609
  throw std::invalid_argument("The types of vectors don't match!");
608
610
 
609
- py::array_t<dense_key_t> keys_py({dataset_count, static_cast<Py_ssize_t>(wanted)});
610
- py::array_t<distance_t> distances_py({dataset_count, static_cast<Py_ssize_t>(wanted)});
611
- py::array_t<Py_ssize_t> counts_py(dataset_count);
611
+ py::array_t<dense_key_t> keys_py({queries_count, static_cast<Py_ssize_t>(wanted)});
612
+ py::array_t<distance_t> distances_py({queries_count, static_cast<Py_ssize_t>(wanted)});
613
+ py::array_t<Py_ssize_t> counts_py(queries_count);
612
614
 
613
615
  std::size_t dimensions = static_cast<std::size_t>(queries_dimensions);
614
616
  metric_t metric = //
@@ -1042,7 +1044,8 @@ PYBIND11_MODULE(compiled, m) {
1042
1044
  py::arg("metric_kind") = metric_kind_t::cos_k, //
1043
1045
  py::arg("metric_signature") = metric_signature_t::array_array_k, //
1044
1046
  py::arg("metric_pointer") = 0, //
1045
- py::arg("multi") = false //
1047
+ py::arg("multi") = false, //
1048
+ py::arg("enable_key_lookups") = true //
1046
1049
  );
1047
1050
 
1048
1051
  i.def( //
@@ -133,13 +133,6 @@
133
133
  "plt.tight_layout()\n",
134
134
  "plt.show()"
135
135
  ]
136
- },
137
- {
138
- "cell_type": "code",
139
- "execution_count": null,
140
- "metadata": {},
141
- "outputs": [],
142
- "source": []
143
136
  }
144
137
  ],
145
138
  "metadata": {