#![warn(missing_debug_implementations, missing_docs)]
use std::collections::VecDeque;
use crate::{
neighbor::{Neighbor, NeighborPriorityQueue},
utils::{VectorId, object_pool::AsPooled},
};
use hashbrown::HashSet;
pub const GRAPH_SLACK_FACTOR: f64 = 1.3_f64;
#[derive(Debug)]
pub struct SearchScratch<I, Q = NeighborPriorityQueue<I>>
where
I: VectorId,
{
pub best: Q,
pub visited: HashSet<I>,
pub id_scratch: Vec<I>,
pub beam_nodes: Vec<I>,
pub range_frontier: VecDeque<I>,
pub in_range: Vec<Neighbor<I>>,
pub hops: u32,
pub cmps: u32,
}
#[derive(Debug, Clone, Copy)]
pub enum PriorityQueueConfiguration {
Fixed(usize),
Resizable(usize),
}
impl<I> SearchScratch<I, NeighborPriorityQueue<I>>
where
I: VectorId,
{
pub fn new(nbest: PriorityQueueConfiguration, size_hint: Option<usize>) -> Self {
let visited = match size_hint {
Some(size_hint) => HashSet::with_capacity(size_hint),
None => HashSet::new(),
};
let best = match nbest {
PriorityQueueConfiguration::Fixed(capacity) => NeighborPriorityQueue::new(capacity),
PriorityQueueConfiguration::Resizable(capacity) => {
NeighborPriorityQueue::auto_resizable_with_search_param_l(capacity)
}
};
Self {
best,
visited,
id_scratch: Vec::new(),
beam_nodes: Vec::new(),
in_range: Vec::new(),
range_frontier: VecDeque::new(),
hops: 0,
cmps: 0,
}
}
pub fn resize(&mut self, nbest: usize) {
self.best.reconfigure(nbest);
}
pub fn clear(&mut self) {
self.best.clear();
self.visited.clear();
self.id_scratch.clear();
self.beam_nodes.clear();
self.in_range.clear();
self.range_frontier.clear();
self.hops = 0;
self.cmps = 0;
}
pub fn search_l(&self) -> usize {
self.best.search_l()
}
}
pub(crate) fn estimate_node_visited_set_size(max_degree: usize, search_list_size: usize) -> usize {
const MARGIN_FACTOR: f64 = 1.1;
(MARGIN_FACTOR * max_degree as f64 * GRAPH_SLACK_FACTOR * search_list_size as f64).ceil()
as usize
}
impl<I> AsPooled<&SearchScratchParams> for SearchScratch<I, NeighborPriorityQueue<I>>
where
I: VectorId,
{
fn create(param: &SearchScratchParams) -> Self {
SearchScratch::new(
PriorityQueueConfiguration::Fixed(param.l_value + param.num_frozen_pts),
Some(estimate_node_visited_set_size(
param.max_degree,
param.l_value,
)),
)
}
fn modify(&mut self, param: &SearchScratchParams) {
self.clear();
if self.best.is_resizable() {
self.best = NeighborPriorityQueue::new(param.l_value + param.num_frozen_pts);
} else {
self.best.reconfigure(param.l_value + param.num_frozen_pts);
}
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct SearchScratchParams {
pub l_value: usize,
pub max_degree: usize,
pub num_frozen_pts: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test_new() {
{
let x = SearchScratch::<u64, NeighborPriorityQueue<u64>>::new(
PriorityQueueConfiguration::Fixed(10),
None,
);
assert!(!x.best.is_resizable());
assert_eq!(x.search_l(), 10);
assert_eq!(x.best.search_l(), 10);
assert_eq!(x.visited.capacity(), 0);
assert!(x.visited.is_empty());
assert!(x.id_scratch.is_empty());
assert!(x.hops == 0);
assert!(x.cmps == 0);
}
{
let x = SearchScratch::<u64, NeighborPriorityQueue<u64>>::new(
PriorityQueueConfiguration::Resizable(10),
None,
);
assert!(x.best.is_resizable());
assert_eq!(x.search_l(), 10);
assert_eq!(x.best.search_l(), 10);
assert_eq!(x.visited.capacity(), 0);
assert!(x.visited.is_empty());
assert!(x.id_scratch.is_empty());
assert!(x.hops == 0);
assert!(x.cmps == 0);
}
}
#[test]
pub fn test_resize() {
let mut x = SearchScratch::<u64, _>::new(PriorityQueueConfiguration::Fixed(5), None);
assert_eq!(x.search_l(), 5);
x.resize(10);
assert_eq!(x.search_l(), 10);
}
#[test]
pub fn test_reconfigure() {
let mut x = SearchScratch::<u64, NeighborPriorityQueue<u64>>::new(
PriorityQueueConfiguration::Fixed(5),
None,
);
assert_eq!(x.search_l(), 5);
x.resize(10);
assert_eq!(x.search_l(), 10);
}
#[test]
pub fn test_clear() {
let mut x = SearchScratch::<u64, NeighborPriorityQueue<u64>>::new(
PriorityQueueConfiguration::Fixed(5),
None,
);
x.visited.insert(1);
x.visited.insert(10);
x.id_scratch.push(1);
x.id_scratch.push(10);
x.best.insert(Neighbor::new(1, 1.0));
x.best.insert(Neighbor::new(10, 2.0));
assert_eq!(x.best.size(), 2);
x.clear();
assert!(x.visited.is_empty());
assert!(x.id_scratch.is_empty());
assert_eq!(x.best.size(), 0);
assert!(x.hops == 0);
assert!(x.cmps == 0);
}
}