Files
faiss/tests/test_binary_hashindex.py
Tia Lim 0f0f728166 Format faiss Python files with black (line length 80) (#5349)
Summary:
Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5349

# this is a no-op change

Ran `black -l 80` over all tracked Python files under `fbcode/faiss/`, using line length 80 to match faiss's documented coding style (`CONTRIBUTING.md`: "80 character line length (both for C++ and Python)"). 172 files were reformatted; 28 were already compliant.

This is a pure-formatting change. `black` runs its AST-equivalence safety check on every file, so no identifiers, string/numeric values, operators, or control flow were altered — only whitespace, indentation, line wrapping, blank lines, trailing commas, and string-quote normalization (single to double quotes).

`fbcode/faiss/` is intentionally excluded from Meta's standard Python autoformatter (`arc f` / `arc lint -a` report no formatter for these paths) because it mirrors to public GitHub, so the Python had drifted out of any consistent style. The C++ is already clang-format-clean (0 of 807 files change), so this effort is Python-only. This diff brings the Python to canonical `black` formatting.

Excluded `demos/index_pq_flat_separate_codes_from_codebook.py`: it contains a Jupyter `!rm` shell-magic line and is not valid Python, so `black` cannot parse it.

This is the base of a 3-diff stack: (1) this black reformat; (2) `.flake8` alignment (max-line-length 80 + black-compatible ignores E203/W503); (3) genuine lint fixes (F401/F403/F841/B011).

Reviewed By: mnorris11

Differential Revision: D109736099

fbshipit-source-id: 9a054635766ed11aa3760a788395babd3d198abf
2026-06-29 17:29:05 -07:00

231 lines
6.6 KiB
Python

# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
import unittest
import numpy as np
import faiss
from common_faiss_tests import for_all_simd_levels, make_binary_dataset
def bitvec_shuffle(a, order):
n, d = a.shape
(db,) = order.shape
b = np.empty((n, db // 8), dtype="uint8")
faiss.bitvec_shuffle(
n,
d * 8,
db,
faiss.swig_ptr(order),
faiss.swig_ptr(a),
faiss.swig_ptr(b),
)
return b
class TestSmallFuncs(unittest.TestCase):
def test_shuffle(self):
d = 256
n = 1000
rs = np.random.RandomState(123)
o = rs.permutation(d).astype("int32")
x = rs.randint(256, size=(n, d // 8)).astype("uint8")
y1 = bitvec_shuffle(x, o[:128])
y2 = bitvec_shuffle(x, o[128:])
y = np.hstack((y1, y2))
oinv = np.empty(d, dtype="int32")
oinv[o] = np.arange(d)
z = bitvec_shuffle(y, oinv)
np.testing.assert_array_equal(x, z)
@for_all_simd_levels
class TestRange(unittest.TestCase):
def test_hash(self):
d = 128
nq = 100
nb = 2000
(_, xb, xq) = make_binary_dataset(d, 0, nb, nq)
index_ref = faiss.IndexBinaryFlat(d)
index_ref.add(xb)
radius = 55
Lref, Dref, Iref = index_ref.range_search(xq, radius)
index = faiss.IndexBinaryHash(d, 10)
index.add(xb)
# index.display()
nfound = []
ndis = []
stats = faiss.cvar.indexBinaryHash_stats
for n_bitflips in range(index.b + 1):
index.nflip = n_bitflips
stats.reset()
Lnew, Dnew, Inew = index.range_search(xq, radius)
for i in range(nq):
ref = Iref[Lref[i] : Lref[i + 1]]
new = Inew[Lnew[i] : Lnew[i + 1]]
snew = set(new)
# no duplicates
self.assertTrue(len(new) == len(snew))
# subset of real results
self.assertTrue(snew <= set(ref))
nfound.append(Lnew[-1])
ndis.append(stats.ndis)
nfound = np.array(nfound)
self.assertTrue(nfound[-1] == Lref[-1])
self.assertTrue(np.all(nfound[1:] >= nfound[:-1]))
def test_multihash(self):
d = 128
nq = 100
nb = 2000
(_, xb, xq) = make_binary_dataset(d, 0, nb, nq)
index_ref = faiss.IndexBinaryFlat(d)
index_ref.add(xb)
radius = 55
Lref, Dref, Iref = index_ref.range_search(xq, radius)
nfound = []
ndis = []
for nh in 1, 3, 5:
index = faiss.IndexBinaryMultiHash(d, nh, 10)
index.add(xb)
# index.display()
stats = faiss.cvar.indexBinaryHash_stats
index.nflip = 2
stats.reset()
Lnew, Dnew, Inew = index.range_search(xq, radius)
for i in range(nq):
ref = Iref[Lref[i] : Lref[i + 1]]
new = Inew[Lnew[i] : Lnew[i + 1]]
snew = set(new)
# no duplicates
self.assertTrue(len(new) == len(snew))
# subset of real results
self.assertTrue(snew <= set(ref))
nfound.append(Lnew[-1])
ndis.append(stats.ndis)
nfound = np.array(nfound)
# self.assertTrue(nfound[-1] == Lref[-1])
self.assertTrue(np.all(nfound[1:] >= nfound[:-1]))
@for_all_simd_levels
class TestKnn(unittest.TestCase):
def test_hash_and_multihash(self):
d = 128
nq = 100
nb = 2000
(_, xb, xq) = make_binary_dataset(d, 0, nb, nq)
index_ref = faiss.IndexBinaryFlat(d)
index_ref.add(xb)
k = 10
Dref, Iref = index_ref.search(xq, k)
nfound = {}
for nh in 0, 1, 3, 5:
for nbit in 4, 7:
if nh == 0:
index = faiss.IndexBinaryHash(d, nbit)
else:
index = faiss.IndexBinaryMultiHash(d, nh, nbit)
index.add(xb)
index.nflip = 2
Dnew, Inew = index.search(xq, k)
nf = 0
for i in range(nq):
ref = Iref[i]
new = Inew[i]
snew = set(new)
# no duplicates
self.assertTrue(len(new) == len(snew))
nf += len(set(ref) & snew)
nfound[(nh, nbit)] = nf
self.assertGreater(nfound[(nh, 4)], nfound[(nh, 7)])
# test serialization
index2 = faiss.deserialize_index_binary(
faiss.serialize_index_binary(index)
)
D2, I2 = index2.search(xq, k)
np.testing.assert_array_equal(Inew, I2)
np.testing.assert_array_equal(Dnew, D2)
# Verify deserialized index is serializable again
index3 = faiss.deserialize_index_binary(
faiss.serialize_index_binary(index2)
)
D3, I3 = index3.search(xq, k)
np.testing.assert_array_equal(Inew, I3)
np.testing.assert_array_equal(Dnew, D3)
self.assertGreater(3, abs(nfound[(0, 7)] - nfound[(1, 7)]))
self.assertGreater(nfound[(3, 7)], nfound[(1, 7)])
self.assertGreater(nfound[(5, 7)], nfound[(3, 7)])
def subtest_result_order(self, nh):
d = 128
nq = 10
nb = 200
(_, xb, xq) = make_binary_dataset(d, 0, nb, nq)
nbit = 10
if nh == 0:
index = faiss.IndexBinaryHash(d, nbit)
else:
index = faiss.IndexBinaryMultiHash(d, nh, nbit)
index.add(xb)
index.nflip = 5
k = 10
Do, Io = index.search(xq, k)
self.assertTrue(np.all(Do[:, 1:] >= Do[:, :-1]))
def test_result_order_binhash(self):
self.subtest_result_order(0)
def test_result_order_multihash(self):
self.subtest_result_order(3)
"""
I suspect this test crashes CircleCI on Linux
# this is an expensive test, so we don't run it by default
class TestLargeIndexWrite: # (unittest.TestCase):
def test_write_580M(self):
dim = 8
nhash = 1
num_million = 580 # changing to 570 works
index1 = faiss.IndexBinaryMultiHash(dim, nhash, int(dim/nhash))
random_hash_codes = np.random.randint(0, 256, (
num_million * int(1e6), int(dim/8))).astype("uint8")
index1.add(random_hash_codes)
faiss.write_index_binary(index1, "/tmp/tmp.faiss")
index2 = faiss.read_index_binary("/tmp/tmp.faiss")
"""