Files
faiss/tests/test_refine.py
T
Alibek Zhakubayev a238933d17 Finish E501 line-length lint sweep across faiss (183 violations, 52 files) (#5444)
Summary:
Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5444

We fix every remaining flake8 E501 (line > 80 chars) violation in
fbcode/faiss/ (excluding gpu/), closing out a lint-cleanup initiative that a
June 29, 2026 diff stack (9a05463576 -> d304e29b9a -> 7d140d86ca) explicitly
promised but left 41% incomplete. The middle diff in that stack aligned
.flake8's max-line-length to black's 80-char width and stated the remaining
277 violations after the config change were "genuine lint (E501 long
strings/URLs, F401/F841/B011, etc.), addressed in later commits in this
stack." The follow-up commit fixed only the F401/F403/F841/B011 categories
(45 issues) and never touched E501, leaving 192 violations across 53 files
live in trunk. A direct re-scan today found 201 violations across 54 files
(two files gained lines since the original count).

We fixed 183 violations across 52 files. Two of the originally-flagged
files, benchs/bench_rabitq.py and
demos/index_pq_flat_separate_codes_from_codebook.py, already carry a
file-level `# flake8: noqa` directive (the second is a Bento/Grimaldi
notebook export that also fails standalone py_compile due to embedded shell
magic) -- flake8 ignores both files entirely, so no changes were needed
there and they are excluded from this diff.

Fix approach, by category:
- The large majority of violations are prose: docstrings and comments
  rewrapped at word boundaries, preserving each file's existing indentation
  and docstring conventions. No wording was changed.
- Code lines (function calls, f-strings, assert messages, long argument
  lists) were reflowed in black-compatible style: parenthesization, comma
  breaks, or implicit adjacent string-literal concatenation. Every touched
  file is confirmed unchanged by `black --line-length 80 --diff` after the
  edit.
- A small number of genuinely unwrappable long URLs (no reasonable break
  point) were suppressed with `# noqa: E501` rather than force-wrapped,
  matching the precedent set by the original black-format diff stack.
- demos/offline_ivf/offline_ivf.py: a repeated, non-trivial ratio expression
  used in both an assert condition and its (lazily-evaluated) failure
  message was hoisted into a single local variable, eliminating the
  duplicate computation. The expression is pure and deterministic (built
  from already-computed arrays), so this is behaviorally identical to the
  original -- not a functional change.
- `arc lint -a` additionally auto-fixed a real bug it detected while
  processing these files: `raise NotImplemented` -> `raise
  NotImplementedError` in contrib/evaluation.py (the sentinel object
  NotImplemented is not an exception type; raising it produces a TypeError
  masking the intended NotImplementedError). It also added a missing
  trailing newline in benchs/fb/bench_index_binary_from_float.py and, in
  the same file, rewrote the `#!/usr/bin/env python2` shebang to
  `#!/usr/bin/env fbpython` -- that rewrite was reverted by hand, since the
  file still does `import cPickle` (a Python 2-only module), so the
  auto-fix would have made the shebang describe an interpreter the script
  cannot actually run under. Fixing the underlying py2-to-py3 migration is
  out of scope for this line-length-only sweep.
- Rebasing onto master (this diff's base commit had drifted) surfaced two
  concurrent upstream fixes to `check_ref_knn_with_draws(...)` calls in
  tests/test_ivf_flat_panorama.py and tests/test_refine_panorama.py (an
  unrelated in-flight correctness fix passing `D_panorama`/`atol` instead
  of a stale `D_regular` arg with no `atol`). The rebase took upstream's
  corrected arguments and re-applied only this diff's line-wrap on top.

We verified zero functional change with an AST-level check: every string
and f-string literal in every touched file was extracted before and after
the edit and compared with whitespace normalized, confirming no wording,
value, or expression was altered beyond the intended `# noqa: E501`
suffixes and the one documented ratio-hoisting simplification above.

Reviewed By: mnorris11

Differential Revision: D112525458

fbshipit-source-id: e4b85d73dafcaac779f3522a383a66e490fa942a
2026-07-17 10:20:42 -07:00

263 lines
9.5 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 numpy as np
import unittest
import faiss
from common_faiss_tests import for_all_simd_levels
from faiss.contrib import datasets, evaluation
@for_all_simd_levels
class TestDistanceComputer(unittest.TestCase):
def do_test(self, factory_string, metric_type=faiss.METRIC_L2):
ds = datasets.SyntheticDataset(32, 1000, 200, 20)
index = faiss.index_factory(32, factory_string, metric_type)
index.train(ds.get_train())
index.add(ds.get_database())
xq = ds.get_queries()
Dref, Iref = index.search(xq, 10)
for is_FlatCodesDistanceComputer in False, True:
if not is_FlatCodesDistanceComputer:
dc = index.get_distance_computer()
else:
if not isinstance(index, faiss.IndexFlatCodes):
continue
dc = index.get_FlatCodesDistanceComputer()
self.assertTrue(dc.this.own())
for q in range(ds.nq):
dc.set_query(faiss.swig_ptr(xq[q]))
for j in range(10):
ref_dis = Dref[q, j]
new_dis = dc(int(Iref[q, j]))
np.testing.assert_almost_equal(new_dis, ref_dis, decimal=5)
def test_distance_computer_PQ(self):
self.do_test("PQ8np")
def test_distance_computer_SQ(self):
self.do_test("SQ8")
def test_distance_computer_SQ6(self):
self.do_test("SQ6")
def test_distance_computer_PQbit6(self):
self.do_test("PQ8x6np")
def test_distance_computer_PQbit6_ip(self):
self.do_test("PQ8x6np", faiss.METRIC_INNER_PRODUCT)
def test_distance_computer_VT(self):
self.do_test("PCA20,SQ8")
def test_distance_computer_AQ_decompress(self):
self.do_test("RQ3x4") # test decompress path
def test_distance_computer_AQ_LUT(self):
self.do_test("RQ3x4_Nqint8") # test LUT path
def test_distance_computer_AQ_LUT_IP(self):
self.do_test("RQ3x4_Nqint8", faiss.METRIC_INNER_PRODUCT)
@for_all_simd_levels
class TestIndexRefineSearchParams(unittest.TestCase):
def do_test(self, factory_string):
ds = datasets.SyntheticDataset(32, 256, 100, 40)
index = faiss.index_factory(32, factory_string)
index.train(ds.get_train())
index.add(ds.get_database())
# Set nprobe on the base index (for IndexRefine, nprobe belongs to
# the IVF base index)
if hasattr(index, "base_index") and hasattr(index.base_index, "nprobe"):
index.base_index.nprobe = 4
elif hasattr(index, "nprobe"):
index.nprobe = 4
xq = ds.get_queries()
# do a search with k_factor = 1
D1, I1 = index.search(xq, 10)
inter1 = faiss.eval_intersection(I1, ds.get_groundtruth(10))
# do a search with k_factor = 1.5
params = faiss.IndexRefineSearchParameters(k_factor=1.1)
D2, I2 = index.search(xq, 10, params=params)
inter2 = faiss.eval_intersection(I2, ds.get_groundtruth(10))
# do a search with k_factor = 2
params = faiss.IndexRefineSearchParameters(k_factor=2)
D3, I3 = index.search(xq, 10, params=params)
inter3 = faiss.eval_intersection(I3, ds.get_groundtruth(10))
# make sure that the recall rate increases with k_factor
self.assertGreater(inter2, inter1)
self.assertGreater(inter3, inter2)
# make sure that the baseline k_factor is unchanged
self.assertEqual(index.k_factor, 1)
# try passing params for the baseline index, change nprobe
base_params = faiss.IVFSearchParameters(nprobe=10)
params = faiss.IndexRefineSearchParameters(
k_factor=1, base_index_params=base_params
)
D4, I4 = index.search(xq, 10, params=params)
inter4 = faiss.eval_intersection(I4, ds.get_groundtruth(10))
base_params = faiss.IVFSearchParameters(nprobe=2)
params = faiss.IndexRefineSearchParameters(
k_factor=1, base_index_params=base_params
)
D5, I5 = index.search(xq, 10, params=params)
inter5 = faiss.eval_intersection(I5, ds.get_groundtruth(10))
# make sure that the recall rate changes
self.assertNotEqual(inter4, inter5)
def test_rflat(self):
# flat is handled by the IndexRefineFlat class
self.do_test("IVF8,PQ2x4np,RFlat")
def test_refine_sq8(self):
# this case uses the IndexRefine class
self.do_test("IVF8,PQ2x4np,Refine(SQ8)")
@for_all_simd_levels
class TestIndexRefineRangeSearch(unittest.TestCase):
def do_test(self, factory_string):
d = 32
radius = 8
ds = datasets.SyntheticDataset(d, 1024, 512, 256)
index = faiss.index_factory(d, factory_string)
index.train(ds.get_train())
index.add(ds.get_database())
xq = ds.get_queries()
xb = ds.get_database()
# perform a range_search
lims_1, D1, I1 = index.range_search(xq, radius)
# create a baseline (FlatL2)
index_flat = faiss.IndexFlatL2(d)
index_flat.train(ds.get_train())
index_flat.add(ds.get_database())
lims_ref, Dref, Iref = index_flat.range_search(xq, radius)
# add a refine index on top of the index
index_r = faiss.IndexRefine(index, index_flat)
lims_2, D2, I2 = index_r.range_search(xq, radius)
# validate: refined range_search() keeps indices untouched
precision_1, recall_1 = evaluation.range_PR(lims_ref, Iref, lims_1, I1)
precision_2, recall_2 = evaluation.range_PR(lims_ref, Iref, lims_2, I2)
self.assertAlmostEqual(recall_1, recall_2)
# validate: refined range_search() updates distances, and new
# distances are correct L2 distances
for iq in range(0, ds.nq):
start_lim = lims_2[iq]
end_lim = lims_2[iq + 1]
for i_lim in range(start_lim, end_lim):
idx = I2[i_lim]
l2_dis = np.sum(
np.square(xq[iq : iq + 1,] - xb[idx : idx + 1,])
)
self.assertAlmostEqual(l2_dis, D2[i_lim], places=4)
# every returned result must be within the radius: candidates
# picked by approximate distance can exceed it once refined,
# and those must be dropped (issue #5367)
self.assertLessEqual(D2[i_lim], radius)
def test_refine_1(self):
self.do_test("SQ4")
def test_refine_sq6(self):
# coarser quantization makes approximate/exact distance mismatches
# near the radius boundary more likely, exercising the filtering
self.do_test("SQ6")
def do_test_k_factor(
self,
factory_string,
metric=faiss.METRIC_L2,
radius=8,
):
d = 32
ds = datasets.SyntheticDataset(d, 1024, 512, 256)
index = faiss.index_factory(d, factory_string, metric)
index.train(ds.get_train())
index.add(ds.get_database())
xq = ds.get_queries()
is_similarity = metric == faiss.METRIC_INNER_PRODUCT
index_flat = (
faiss.IndexFlatIP(d) if is_similarity else faiss.IndexFlatL2(d)
)
index_flat.add(ds.get_database())
lims_ref, Dref, Iref = index_flat.range_search(xq, radius)
# guard against a vacuous test: the radius must select some but not all
# candidates, otherwise the exact filter and k_factor widening below are
# never meaningfully exercised
self.assertGreater(len(Iref), 0)
self.assertLess(len(Iref), ds.nq * ds.nb)
index_r = faiss.IndexRefine(index, index_flat)
# k_factor scales the radius used to query the base index (base_radius =
# radius * k_factor); the results are still filtered at the requested
# radius, so every returned distance must stay on the correct side of it
# regardless of k_factor (issue #5367). For an L2 base a larger k_factor
# widens the base search and can only add candidates, so recall must not
# decrease; for a similarity base radius * k_factor does not necessarily
# widen the search, so no recall guarantee is asserted there.
recalls = []
for k_factor in (1, 4):
index_r.k_factor = k_factor
lims, D, I = index_r.range_search(xq, radius)
_, recall = evaluation.range_PR(lims_ref, Iref, lims, I)
recalls.append(recall)
for iq in range(ds.nq):
for i_lim in range(lims[iq], lims[iq + 1]):
if is_similarity:
self.assertGreaterEqual(D[i_lim], radius)
else:
self.assertLessEqual(D[i_lim], radius)
if not is_similarity:
# widening the L2 base radius does not lose any results
self.assertGreaterEqual(recalls[1] + 1e-6, recalls[0])
def test_refine_k_factor(self):
self.do_test_k_factor("SQ4")
def test_refine_k_factor_ip(self):
# an inner-product base exercises the is_similarity (CMin) branch of the
# exact radius filter, with both a positive and a negative radius.
# Inner products on this dataset span roughly [-5, +5], so these radii
# select non-trivial result sets on either side of zero.
self.do_test_k_factor("SQ4", faiss.METRIC_INNER_PRODUCT, radius=4)
self.do_test_k_factor("SQ4", faiss.METRIC_INNER_PRODUCT, radius=-2)