Skip to main content

iqdb_distance/
lib.rs

1//! # iqdb-distance
2//!
3//! Distance and similarity functions for the **iQDB** vector-database
4//! spine. The crate owns the math: given two `&[f32]` slices and a
5//! [`iqdb_types::DistanceMetric`] it computes the requested distance and
6//! returns it as `f32`. The five metrics โ€” Cosine, DotProduct, Euclidean
7//! (L2), Manhattan (L1), and Hamming โ€” sit behind the single [`Distance`]
8//! trait, with one zero-sized type per metric ([`Cosine`], [`DotProduct`],
9//! [`Euclidean`], [`Manhattan`], [`Hamming`]). The top-level [`compute`] and
10//! [`compute_batch`] functions take the metric tag and route to the right
11//! implementation.
12//!
13//! For pre-normalized embeddings, [`cosine_normalized`] is a fast path that
14//! skips the norm and division (`1 - a ยท b`), and [`normalize`] produces the
15//! unit vectors it expects.
16//!
17//! Performance: every distance call is allocation-free ([`normalize`], which
18//! returns a new vector, is the sole documented exception). SIMD kernels (AVX2
19//! on x86_64, NEON on aarch64) are picked at runtime from [`detect_features`]
20//! and short-circuit to the scalar reference when the host lacks the feature
21//! or when `force_scalar` has been called.
22//!
23//! ## Example
24//!
25//! ```
26//! use iqdb_distance::{Cosine, Distance};
27//!
28//! let a = [1.0_f32, 0.0, 0.0];
29//! let b = [0.0_f32, 1.0, 0.0];
30//!
31//! // Perpendicular unit vectors -> cosine distance ~ 1.0.
32//! let d = Cosine::compute(&a, &b).expect("non-empty, same length");
33//! assert!((d - 1.0).abs() < 1e-6);
34//! ```
35//!
36//! ## Errors
37//!
38//! Every fallible call returns [`iqdb_types::Result`]. Empty inputs and
39//! length mismatches surface as [`iqdb_types::IqdbError::InvalidVector`] and
40//! [`iqdb_types::IqdbError::DimensionMismatch`] respectively; the library
41//! never panics on bad input.
42
43#![cfg_attr(docsrs, feature(doc_cfg))]
44#![deny(warnings)]
45#![deny(missing_docs)]
46#![deny(unsafe_op_in_unsafe_fn)]
47#![deny(unused_must_use)]
48#![deny(unused_results)]
49#![deny(clippy::unwrap_used)]
50#![deny(clippy::expect_used)]
51#![deny(clippy::todo)]
52#![deny(clippy::unimplemented)]
53#![deny(clippy::print_stdout)]
54#![deny(clippy::print_stderr)]
55#![deny(clippy::dbg_macro)]
56#![deny(clippy::unreachable)]
57#![deny(clippy::undocumented_unsafe_blocks)]
58
59mod dispatch;
60mod features;
61mod metrics;
62mod normalized;
63mod scalar;
64mod simd;
65mod traits;
66mod validate;
67
68#[cfg(any(test, feature = "testing"))]
69pub use crate::dispatch::compute_scalar;
70pub use crate::dispatch::{compute, compute_batch};
71pub use crate::features::{CpuFeatures, detect_features, forced_scalar};
72#[cfg(any(test, feature = "testing"))]
73pub use crate::features::{force_scalar, which_kernel};
74pub use crate::metrics::{Cosine, DotProduct, Euclidean, Hamming, Manhattan};
75pub use crate::normalized::{cosine_normalized, normalize};
76pub use crate::traits::Distance;
77
78/// The version of this crate, taken from `Cargo.toml` at compile time.
79///
80/// Exposed so a consumer can report the exact `iqdb-distance` build it links
81/// against โ€” useful in diagnostics and version-skew checks across the iqdb
82/// crate family.
83///
84/// # Examples
85///
86/// ```
87/// // Carries a `major.minor.patch` SemVer core.
88/// let version = iqdb_distance::VERSION;
89/// assert_eq!(version.split('.').count(), 3);
90/// assert!(version.split('.').all(|part| !part.is_empty()));
91/// ```
92pub const VERSION: &str = env!("CARGO_PKG_VERSION");