use std::sync::atomic::AtomicBool;
use crate::common::bitvec::BitSlice;
use crate::common::condition_checker::{CheckItem, ConditionChecker, Rest, Select};
use crate::common::counter::hardware_counter::HardwareCounterCell;
use crate::common::fixed_length_priority_queue::FixedLengthPriorityQueue;
use crate::common::generic_consts::Random;
use crate::common::types::{PointOffsetType, ScoreType, ScoredPointOffset};
use smallvec::SmallVec;
use crate::segment::common::operation_error::{OperationError, OperationResult, check_process_stopped};
use crate::segment::data_types::vectors::QueryVector;
use crate::segment::index::query_optimization::optimized_filter::OptimizedFilter;
use crate::segment::vector_storage::common::VECTOR_READ_BATCH_SIZE;
use crate::segment::vector_storage::quantized::quantized_query_scorer::InternalScorerUnsupported;
use crate::segment::vector_storage::quantized::quantized_vectors::QuantizedVectorsRead;
use crate::segment::vector_storage::query_scorer::QueryScorerBytes;
use crate::segment::vector_storage::{NotDeletedChecker, RawScorer, RawScorerBuilder, VectorStorageRead};
#[cfg(feature = "testing")]
use crate::segment::vector_storage::{VectorStorageEnum, new_raw_scorer};
pub struct FilteredScorer<'a> {
raw_scorer: Box<dyn RawScorer + 'a>,
filters: ScorerFilters<'a>,
scores_buffer: Vec<ScoreType>,
}
pub struct ScorerFilters<'a> {
filter_context: Option<OptimizedFilter<'a>>,
deleted: NotDeletedChecker<'a>,
}
impl<'a> ScorerFilters<'a> {
pub fn new(
filter_context: Option<OptimizedFilter<'a>>,
deleted: NotDeletedChecker<'a>,
) -> Self {
ScorerFilters {
filter_context,
deleted,
}
}
pub fn check_vector(&self, point_id: PointOffsetType) -> bool {
self.deleted.check_infallible(point_id)
&& self
.filter_context
.as_ref()
.is_none_or(|f| f.check_infallible(point_id))
}
}
impl ConditionChecker for ScorerFilters<'_> {
type Error = OperationError;
fn check(&self, point_id: PointOffsetType) -> OperationResult<bool> {
Ok(self.deleted.check(point_id)?
&& match &self.filter_context {
Some(f) => f.check(point_id)?,
None => true,
})
}
fn check_infallible(&self, point_id: PointOffsetType) -> bool {
self.check_vector(point_id)
}
#[inline]
fn check_batched<K: CheckItem>(
&self,
ids: &mut [K],
select: Select,
rest: Rest,
) -> OperationResult<usize> {
let Self {
filter_context,
deleted,
} = self;
match select {
Select::Matches => {
let n = deleted.check_batched(ids, Select::Matches, rest)?;
match filter_context {
Some(f) => f.check_batched(&mut ids[..n], Select::Matches, rest),
None => Ok(n),
}
}
Select::NonMatches => {
let deleted_rest = rest.keep_if(filter_context.is_some());
let mut f = deleted.check_batched(ids, Select::NonMatches, deleted_rest)?;
if let Some(filter) = filter_context {
f += filter.check_batched(&mut ids[f..], Select::NonMatches, rest)?;
}
Ok(f)
}
}
}
}
pub struct FilteredBytesScorer<'a> {
scorer_bytes: &'a dyn QueryScorerBytes,
filters: &'a ScorerFilters<'a>,
}
impl<'a> FilteredBytesScorer<'a> {
pub fn score_points(
&self,
points: &mut Vec<(PointOffsetType, &[u8])>,
limit: usize,
) -> impl Iterator<Item = ScoredPointOffset> {
points.retain(|(point_id, _)| self.filters.check_vector(*point_id));
if limit != 0 {
points.truncate(limit);
}
points.iter().map(|&(idx, bytes)| ScoredPointOffset {
idx,
score: self.scorer_bytes.score_bytes(bytes),
})
}
}
impl<'a> FilteredScorer<'a> {
pub fn new<V, Q>(
query: QueryVector,
vectors: &'a V,
quantized_vectors: Option<&'a Q>,
filter_context: Option<OptimizedFilter<'a>>,
point_deleted: &'a BitSlice,
hardware_counter: HardwareCounterCell,
) -> OperationResult<Self>
where
V: VectorStorageRead + RawScorerBuilder,
Q: QuantizedVectorsRead,
{
let raw_scorer = match quantized_vectors {
Some(quantized_vectors) => quantized_vectors.raw_scorer(query, hardware_counter)?,
None => vectors.build_raw_scorer(query, hardware_counter)?,
};
Ok(FilteredScorer {
raw_scorer,
filters: ScorerFilters::new(filter_context, vectors.not_deleted_checker(point_deleted)),
scores_buffer: Vec::new(),
})
}
pub fn new_internal<V, Q>(
point_id: PointOffsetType,
vectors: &'a V,
quantized_vectors: Option<&'a Q>,
filter_context: Option<OptimizedFilter<'a>>,
point_deleted: &'a BitSlice,
hardware_counter: HardwareCounterCell,
) -> OperationResult<Self>
where
V: VectorStorageRead + RawScorerBuilder,
Q: QuantizedVectorsRead,
{
let original_query_fn = || {
let query = vectors.get_vector::<Random>(point_id);
let query: QueryVector = query.as_vec_ref().into();
query
};
let raw_scorer = match quantized_vectors {
Some(quantized_vectors) => quantized_vectors
.raw_internal_scorer(point_id, hardware_counter)
.or_else(|InternalScorerUnsupported(hardware_counter)| {
quantized_vectors.raw_scorer(original_query_fn(), hardware_counter)
})?,
None => {
let query = original_query_fn();
vectors.build_raw_scorer(query, hardware_counter)?
}
};
Ok(FilteredScorer {
raw_scorer,
filters: ScorerFilters::new(filter_context, vectors.not_deleted_checker(point_deleted)),
scores_buffer: Vec::new(),
})
}
#[cfg(feature = "testing")]
pub fn new_for_test(
vector: QueryVector,
vector_storage: &'a VectorStorageEnum,
point_deleted: &'a BitSlice,
) -> Self {
FilteredScorer {
raw_scorer: new_raw_scorer(vector, vector_storage, HardwareCounterCell::new()).unwrap(),
filters: ScorerFilters::new(None, vector_storage.not_deleted_checker(point_deleted)),
scores_buffer: Vec::new(),
}
}
pub fn raw_scorer(&self) -> &dyn RawScorer {
self.raw_scorer.as_ref()
}
pub fn filters(&self) -> &ScorerFilters<'a> {
&self.filters
}
pub fn scorer_bytes(&self) -> Option<FilteredBytesScorer<'_>> {
Some(FilteredBytesScorer {
scorer_bytes: self.raw_scorer.scorer_bytes()?,
filters: &self.filters,
})
}
#[inline(always)]
pub fn score_points(
&mut self,
point_ids: &mut Vec<PointOffsetType>,
limit: usize,
) -> impl Iterator<Item = ScoredPointOffset> {
let mut n = self
.filters
.check_batched(point_ids, Select::Matches, Rest::Discard)
.unwrap_or(0 );
if limit != 0 {
n = n.min(limit);
}
point_ids.truncate(n);
self.score_points_unfiltered(point_ids)
}
pub fn score_points_unfiltered(
&mut self,
point_ids: &[PointOffsetType],
) -> impl Iterator<Item = ScoredPointOffset> {
if self.scores_buffer.len() < point_ids.len() {
self.scores_buffer.resize(point_ids.len(), 0.0);
}
self.raw_scorer
.score_points(point_ids, &mut self.scores_buffer[..point_ids.len()]);
std::iter::zip(point_ids, &self.scores_buffer)
.map(|(&idx, &score)| ScoredPointOffset { idx, score })
}
pub fn score_point(&self, point_id: PointOffsetType) -> ScoreType {
self.raw_scorer.score_point(point_id)
}
pub fn score_internal(&self, point_a: PointOffsetType, point_b: PointOffsetType) -> ScoreType {
self.raw_scorer.score_internal(point_a, point_b)
}
}
struct BatchSearch<'a> {
raw_scorer: Box<dyn RawScorer + 'a>,
pq: FixedLengthPriorityQueue<ScoredPointOffset>,
}
pub struct BatchFilteredSearcher<'a> {
scorer_batch: SmallVec<[BatchSearch<'a>; 1]>,
filters: ScorerFilters<'a>,
}
impl<'a> BatchFilteredSearcher<'a> {
pub fn new<V, Q>(
queries: &[&QueryVector],
vectors: &'a V,
quantized_vectors: Option<&'a Q>,
filter_context: Option<OptimizedFilter<'a>>,
top: usize,
point_deleted: &'a BitSlice,
hardware_counter: HardwareCounterCell,
) -> OperationResult<Self>
where
V: VectorStorageRead + RawScorerBuilder,
Q: QuantizedVectorsRead,
{
let scorer_batch = queries
.iter()
.map(|&query| {
let query = query.to_owned();
let hardware_counter = hardware_counter.fork();
let raw_scorer = match quantized_vectors {
Some(quantized_vectors) => {
quantized_vectors.raw_scorer(query, hardware_counter)
}
None => vectors.build_raw_scorer(query, hardware_counter),
};
let pq = FixedLengthPriorityQueue::new(top);
raw_scorer.map(|raw_scorer| BatchSearch { raw_scorer, pq })
})
.collect::<Result<_, _>>()?;
let filters =
ScorerFilters::new(filter_context, vectors.not_deleted_checker(point_deleted));
Ok(Self {
scorer_batch,
filters,
})
}
#[cfg(feature = "testing")]
pub fn new_for_test(
vectors: &[QueryVector],
vector_storage: &'a VectorStorageEnum,
point_deleted: &'a BitSlice,
top: usize,
) -> Self {
let scorer_batch = vectors
.iter()
.map(|vector| {
let raw_scorer = new_raw_scorer(
vector.to_owned(),
vector_storage,
HardwareCounterCell::new(),
)
.unwrap();
BatchSearch {
raw_scorer,
pq: FixedLengthPriorityQueue::new(top),
}
})
.collect();
Self {
scorer_batch,
filters: ScorerFilters::new(None, vector_storage.not_deleted_checker(point_deleted)),
}
}
pub fn iter_not_deleted(&self) -> impl Iterator<Item = PointOffsetType> + 'a {
self.filters
.deleted
.point_deleted
.iter_zeros()
.map(|p| p as PointOffsetType)
}
#[cfg(feature = "testing")]
pub fn peek_top_all(
self,
is_stopped: &AtomicBool,
) -> OperationResult<Vec<Vec<ScoredPointOffset>>> {
let iter = self.iter_not_deleted();
self.peek_top_iter(iter, is_stopped)
}
pub fn peek_top_iter(
mut self,
mut points: impl Iterator<Item = PointOffsetType>,
is_stopped: &AtomicBool,
) -> OperationResult<Vec<Vec<ScoredPointOffset>>> {
let mut chunk = [0; VECTOR_READ_BATCH_SIZE];
let mut scores_buffer = [0.0; VECTOR_READ_BATCH_SIZE];
loop {
check_process_stopped(is_stopped)?;
let mut chunk_size = 0;
for point_id in &mut points {
check_process_stopped(is_stopped)?;
if !self.filters.check_vector(point_id) {
continue;
}
chunk[chunk_size] = point_id;
chunk_size += 1;
if chunk_size == VECTOR_READ_BATCH_SIZE {
break;
}
}
if chunk_size == 0 {
break;
}
for BatchSearch { raw_scorer, pq } in &mut self.scorer_batch {
raw_scorer.score_points(&chunk[..chunk_size], &mut scores_buffer[..chunk_size]);
for i in 0..chunk_size {
pq.push(ScoredPointOffset {
idx: chunk[i],
score: scores_buffer[i],
});
}
}
}
let results = self
.scorer_batch
.into_iter()
.map(|BatchSearch { pq, .. }| pq.into_sorted_vec())
.collect();
Ok(results)
}
}