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, ScoredPointOffset};
use parking_lot::RwLock;
use rayon::ThreadPool;
use rayon::iter::{IntoParallelIterator as _, ParallelIterator as _};
use crate::segment::common::operation_error::OperationResult;
use crate::segment::index::hnsw_index::HnswM;
use crate::segment::index::hnsw_index::graph_layers::GraphLayers;
use crate::segment::index::hnsw_index::graph_layers_builder::{GraphLayersBuilder, LockedLayersContainer};
use crate::segment::index::hnsw_index::links_container::{ItemsBuffer, LinksContainer};
use crate::segment::index::visited_pool::VisitedPool;
use crate::segment::vector_storage::quantized::quantized_vectors::QuantizedVectors;
use crate::segment::vector_storage::{RawScorer, VectorStorageEnum, VectorStorageRead, new_raw_scorer};
pub struct GraphLayersHealer<'a> {
links_layers: Vec<LockedLayersContainer>,
to_heal: Vec<(PointOffsetType, usize)>,
old_to_new: &'a [Option<PointOffsetType>],
hnsw_m: HnswM,
ef_construct: usize,
visited_pool: VisitedPool,
}
impl<'a> GraphLayersHealer<'a> {
pub fn new(
graph_layers: &GraphLayers,
old_to_new: &'a [Option<PointOffsetType>],
ef_construct: usize,
) -> Self {
let mut to_heal = Vec::new();
let links_layers = {
graph_layers.links.to_edges_impl(|point_id, level| {
let level_m = graph_layers.hnsw_m.level_m(level);
let mut container = LinksContainer::with_capacity(level_m);
container.fill_from(graph_layers.links.links(point_id, level).take(level_m));
if container
.iter()
.any(|neighbor| old_to_new[neighbor as usize].is_none())
{
to_heal.push((point_id, level));
}
RwLock::new(container)
})
};
Self {
links_layers,
to_heal,
old_to_new,
hnsw_m: graph_layers.hnsw_m,
ef_construct,
visited_pool: VisitedPool::new(),
}
}
fn point_deleted(&self, point: PointOffsetType) -> bool {
self.old_to_new[point as usize].is_none()
}
fn search_shortcuts_on_level(
&self,
offset: PointOffsetType,
level: usize,
scorer: &dyn RawScorer,
) -> FixedLengthPriorityQueue<ScoredPointOffset> {
let mut visited_list = self.visited_pool.get(self.links_layers.len());
let mut nearest = FixedLengthPriorityQueue::<ScoredPointOffset>::new(self.ef_construct);
let limit = self.hnsw_m.level_m(level);
let mut neighbours: Vec<PointOffsetType> = Vec::with_capacity(2 * limit);
let mut scores_buffer = Vec::with_capacity(limit);
let mut pending = Vec::new();
visited_list.check_and_update_visited(offset);
{
let links = self.links_layers[offset as usize][level].read();
for &point in links.links() {
if !self.point_deleted(point) {
visited_list.check_and_update_visited(point);
} else {
pending.push(ScoredPointOffset {
idx: point,
score: scorer.score_point(point),
});
}
}
}
while let Some(candidate) = pending.pop() {
if nearest.is_full() && candidate.score < nearest.top().unwrap().score {
continue;
}
if visited_list.check_and_update_visited(candidate.idx) {
continue;
}
neighbours.clear();
neighbours.extend(
self.links_layers[candidate.idx as usize][level]
.read()
.links()
.iter()
.filter(|&&link| !visited_list.check(link)),
);
if scores_buffer.len() < neighbours.len() {
scores_buffer.resize(neighbours.len(), 0.0);
}
scorer.score_points(&neighbours, &mut scores_buffer[..neighbours.len()]);
for (&idx, &score) in neighbours.iter().zip(&scores_buffer) {
if !self.point_deleted(idx) {
nearest.push(ScoredPointOffset { idx, score });
} else {
pending.push(ScoredPointOffset { idx, score });
}
}
}
nearest
}
fn heal_point_on_level(&self, offset: PointOffsetType, level: usize, scorer: &dyn RawScorer) {
let level_m = self.hnsw_m.level_m(level);
let mut valid_links = Vec::with_capacity(level_m);
valid_links.extend(
self.links_layers[offset as usize][level]
.read()
.links()
.iter()
.filter(|&&idx| !self.point_deleted(idx)),
);
let shortcuts = self.search_shortcuts_on_level(offset, level, scorer);
let mut container = LinksContainer::with_capacity(level_m);
let scorer_fn = |a, b| scorer.score_internal(a, b);
container.fill_from_sorted_with_heuristic(
shortcuts.into_iter_sorted(),
level_m - valid_links.len(),
scorer_fn,
);
for &link in &valid_links {
container.push(link);
}
let container = container.into_vec();
self.links_layers[offset as usize][level]
.write()
.fill_from(container.iter().copied());
let mut items = ItemsBuffer::default();
for other_point in container {
let mut other_container = self.links_layers[other_point as usize][level].write();
if !other_container.iter().any(|link| link == offset) {
other_container.connect_with_heuristic(
offset,
other_point,
level_m,
scorer_fn,
&mut items,
);
}
}
}
pub fn heal(
&mut self,
pool: &ThreadPool,
vector_storage: &VectorStorageEnum,
quantized_vectors: Option<&QuantizedVectors>,
) -> OperationResult<()> {
pool.install(|| {
std::mem::take(&mut self.to_heal)
.into_par_iter()
.try_for_each(|(offset, level)| {
let internal_hardware_counter = HardwareCounterCell::disposable();
let query = vector_storage
.get_vector::<Random>(offset)
.as_vec_ref()
.into();
let scorer = if let Some(quantized_vectors) = quantized_vectors {
quantized_vectors.raw_scorer(query, internal_hardware_counter)?
} else {
new_raw_scorer(query, vector_storage, internal_hardware_counter)?
};
self.heal_point_on_level(offset, level, scorer.as_ref());
Ok(())
})
})
}
pub fn save_into_builder(self, builder: &GraphLayersBuilder) {
for (old_offset, layers) in self.links_layers.into_iter().enumerate() {
let Some(new_offset) = self.old_to_new[old_offset] else {
continue;
};
let links_by_level = layers
.into_iter()
.map(|layer| {
layer
.into_inner()
.into_vec()
.into_iter()
.filter_map(|link| self.old_to_new[link as usize])
.collect()
})
.collect();
builder.add_new_point(new_offset, links_by_level);
}
}
}