mirror of
https://github.com/facebookresearch/faiss.git
synced 2026-10-11 22:50:00 +00:00
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
198 lines
5.7 KiB
Python
198 lines
5.7 KiB
Python
# Copyright (c) Facebook, Inc. and its affiliates.
|
|
#
|
|
# This source code is licensed under the MIT license found in the
|
|
# LICENSE file in the root directory of this source tree.
|
|
|
|
# @nolint
|
|
|
|
# not linting this file because it imports * from swigfaiss, which
|
|
# causes a ton of useless warnings.
|
|
|
|
import numpy as np
|
|
import array
|
|
import warnings
|
|
|
|
from faiss.loader import *
|
|
|
|
###########################################
|
|
# Utility to add a deprecation warning to
|
|
# classes from the SWIG interface
|
|
###########################################
|
|
|
|
|
|
def _make_deprecated_swig_class(deprecated_name, base_name):
|
|
"""
|
|
Dynamically construct deprecated classes as wrappers around renamed ones
|
|
|
|
The deprecation warning added in their __new__-method will trigger upon
|
|
construction of an instance of the class, but only once per session.
|
|
|
|
We do this here (in __init__.py) because the base classes are defined in
|
|
the SWIG interface, making it cumbersome to add the deprecation there.
|
|
|
|
Parameters
|
|
----------
|
|
deprecated_name : string
|
|
Name of the class to be deprecated; _not_ present in SWIG interface.
|
|
base_name : string
|
|
Name of the class that is replacing deprecated_name; must already be
|
|
imported into the current namespace.
|
|
|
|
Returns
|
|
-------
|
|
None
|
|
However, the deprecated class gets added to the faiss namespace
|
|
"""
|
|
base_class = globals()[base_name]
|
|
|
|
def new_meth(cls, *args, **kwargs):
|
|
msg = (
|
|
f"The class faiss.{deprecated_name} is deprecated in favour of "
|
|
f"faiss.{base_name}!"
|
|
)
|
|
warnings.warn(msg, DeprecationWarning, stacklevel=2)
|
|
instance = super(base_class, cls).__new__(cls, *args, **kwargs)
|
|
return instance
|
|
|
|
# three-argument version of "type" uses (name, tuple-of-bases,
|
|
# dict-of-attributes)
|
|
klazz = type(deprecated_name, (base_class,), {"__new__": new_meth})
|
|
|
|
# this ends up adding the class to the "faiss" namespace, in a way that it
|
|
# is available both through "import faiss" and "from faiss import *"
|
|
globals()[deprecated_name] = klazz
|
|
|
|
|
|
###########################################
|
|
# numpy array / std::vector conversions
|
|
###########################################
|
|
|
|
sizeof_long = array.array("l").itemsize
|
|
deprecated_name_map = {
|
|
# deprecated: replacement
|
|
"Float": "Float32",
|
|
"Double": "Float64",
|
|
"Char": "Int8",
|
|
"Int": "Int32",
|
|
"Long": "Int32" if sizeof_long == 4 else "Int64",
|
|
"LongLong": "Int64",
|
|
"Byte": "UInt8",
|
|
# previously misspelled variant
|
|
"Uint64": "UInt64",
|
|
}
|
|
|
|
for depr_prefix, base_prefix in deprecated_name_map.items():
|
|
_make_deprecated_swig_class(depr_prefix + "Vector", base_prefix + "Vector")
|
|
|
|
# same for the three legacy *VectorVector classes
|
|
if depr_prefix in ["Float", "Long", "Byte"]:
|
|
_make_deprecated_swig_class(
|
|
depr_prefix + "VectorVector", base_prefix + "VectorVector"
|
|
)
|
|
|
|
# mapping from vector names in swigfaiss.swig and the numpy dtype names
|
|
# TODO: once deprecated classes are removed, remove the dict and just use
|
|
# .lower() below
|
|
vector_name_map = {
|
|
"Float32": "float32",
|
|
"Float64": "float64",
|
|
"Int8": "int8",
|
|
"Int16": "int16",
|
|
"Int32": "int32",
|
|
"Int64": "int64",
|
|
"UInt8": "uint8",
|
|
"UInt16": "uint16",
|
|
"UInt32": "uint32",
|
|
"UInt64": "uint64",
|
|
**{k: v.lower() for k, v in deprecated_name_map.items()},
|
|
}
|
|
|
|
|
|
def vector_to_array(v):
|
|
"""convert a C++ vector to a numpy array"""
|
|
classname = v.__class__.__name__
|
|
if classname.startswith("AlignedTable"):
|
|
return AlignedTable_to_array(v)
|
|
if classname.startswith("MaybeOwnedVector"):
|
|
dtype = np.dtype(vector_name_map[classname[16:]])
|
|
a = np.empty(v.size(), dtype=dtype)
|
|
if v.size() > 0:
|
|
memcpy(swig_ptr(a), v.data(), a.nbytes)
|
|
return a
|
|
|
|
assert classname.endswith("Vector")
|
|
dtype = np.dtype(vector_name_map[classname[:-6]])
|
|
a = np.empty(v.size(), dtype=dtype)
|
|
if v.size() > 0:
|
|
memcpy(swig_ptr(a), v.data(), a.nbytes)
|
|
return a
|
|
|
|
|
|
def vector_float_to_array(v):
|
|
return vector_to_array(v)
|
|
|
|
|
|
def copy_array_to_vector(a, v):
|
|
"""copy a numpy array to a vector"""
|
|
(n,) = a.shape
|
|
classname = v.__class__.__name__
|
|
if classname.startswith("MaybeOwnedVector"):
|
|
assert v.is_owned, "cannot copy to an non-owned MaybeOwnedVector"
|
|
dtype = np.dtype(vector_name_map[classname[16:]])
|
|
assert (
|
|
dtype == a.dtype
|
|
), "cannot copy a %s array to a %s (should be %s)" % (
|
|
a.dtype,
|
|
classname,
|
|
dtype,
|
|
)
|
|
v.resize(n)
|
|
if n > 0:
|
|
memcpy(v.data(), swig_ptr(a), a.nbytes)
|
|
return
|
|
|
|
assert classname.endswith("Vector")
|
|
dtype = np.dtype(vector_name_map[classname[:-6]])
|
|
assert dtype == a.dtype, "cannot copy a %s array to a %s (should be %s)" % (
|
|
a.dtype,
|
|
classname,
|
|
dtype,
|
|
)
|
|
v.resize(n)
|
|
if n > 0:
|
|
memcpy(v.data(), swig_ptr(a), a.nbytes)
|
|
|
|
|
|
# same for AlignedTable
|
|
|
|
|
|
def copy_array_to_AlignedTable(a, v):
|
|
(n,) = a.shape
|
|
# TODO check class name
|
|
assert v.itemsize() == a.itemsize
|
|
v.resize(n)
|
|
if n > 0:
|
|
memcpy(v.get(), swig_ptr(a), a.nbytes)
|
|
|
|
|
|
def array_to_AlignedTable(a):
|
|
if a.dtype == "uint16":
|
|
v = AlignedTableUint16(a.size)
|
|
elif a.dtype == "uint8":
|
|
v = AlignedTableUint8(a.size)
|
|
else:
|
|
assert False
|
|
copy_array_to_AlignedTable(a, v)
|
|
return v
|
|
|
|
|
|
def AlignedTable_to_array(v):
|
|
"""convert an AlignedTable to a numpy array"""
|
|
classname = v.__class__.__name__
|
|
assert classname.startswith("AlignedTable")
|
|
dtype = classname[12:].lower()
|
|
a = np.empty(v.size(), dtype=dtype)
|
|
if a.size > 0:
|
|
memcpy(swig_ptr(a), v.data(), a.nbytes)
|
|
return a
|