Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions instant-distance/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ where
pub fn get(&self, i: usize, search: &Search) -> Option<MapItem<'_, P, V>> {
Some(MapItem::from(self.hnsw.get(i, search)?, self))
}

pub fn insert(&mut self, point: P, value: V) -> Result<PointId, Box<dyn std::error::Error>> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to ever result an Err, so it doesn't need to be fallible?

let point_id = self.hnsw.insert(point, 100, Some(Heuristic::default()));
self.values.push(value);
Ok(point_id)
}
}

pub struct MapItem<'a, P, V> {
Expand Down Expand Up @@ -394,6 +400,55 @@ where
pub fn get(&self, i: usize, search: &Search) -> Option<Item<'_, P>> {
Some(Item::new(search.nearest.get(i).copied()?, self))
}

pub fn insert(
&mut self,
point: P,
ef_construction: usize,
heuristic: Option<Heuristic>,
) -> PointId {
let new_pid = self.points.len();
let new_point_id = PointId(new_pid as u32);

self.points.push(point);
self.zero.push(ZeroNode::default());

let zeros = self
.zero
.iter()
.map(|z| RwLock::new(z.clone()))
.collect::<Vec<_>>();
Comment on lines +416 to +420
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a pretty expensive operation, to the point that calling insert() one-by-one is unlikely to be very effective. Could we yield some construction type here that can be reused to insert multiple points?


let top = if self.layers.is_empty() {
LayerId(0)
} else {
LayerId(self.layers.len())
};

let construction = Construction {
zero: zeros.as_slice(),
pool: SearchPool::new(self.points.len()),
top,
points: self.points.as_slice(),
heuristic,
ef_construction,
#[cfg(feature = "indicatif")]
progress: None,
#[cfg(feature = "indicatif")]
done: AtomicUsize::new(0),
};

let new_layer = construction.top;
construction.insert(new_point_id, new_layer, &self.layers);

self.zero = construction
.zero
.iter()
.map(|node| node.read().clone())
.collect();

new_point_id
}
}

pub struct Item<'a, P> {
Expand Down
82 changes: 82 additions & 0 deletions instant-distance/tests/all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,85 @@ impl instant_distance::Point for Point {
((self.0 - other.0).powi(2) + (self.1 - other.1).powi(2)).sqrt()
}
}

#[test]
#[allow(clippy::float_cmp, clippy::approx_constant)]
fn incremental_insert() {
let points = (0..4)
.map(|i| Point(i as f32, i as f32))
.collect::<Vec<_>>();
let values = vec!["zero", "one", "two", "three"];
let seed = ThreadRng::default().gen::<u64>();
let builder = Builder::default().seed(seed);

let mut map = builder.build(points, values);

map.insert(Point(4.0, 4.0), "four").expect("Should insert");

let mut search = Search::default();

for (i, item) in map.search(&Point(4.0, 4.0), &mut search).enumerate() {
match i {
0 => {
assert_eq!(item.distance, 0.0);
assert_eq!(item.value, &"four");
}
1 => {
assert_eq!(item.distance, 1.4142135);
assert!(item.value == &"three");
}
2 => {
assert_eq!(item.distance, 2.828427);
assert!(item.value == &"two");
}
3 => {
assert_eq!(item.distance, 4.2426405);
assert!(item.value == &"one");
}
4 => {
assert_eq!(item.distance, 5.656854);
assert!(item.value == &"zero");
}
_ => unreachable!(),
}
}

// Note
// This has the same expected results as incremental_insert but builds
// the whole map in one go. Only here for comparison.
{
let points = (0..5)
.map(|i| Point(i as f32, i as f32))
.collect::<Vec<_>>();
let values = vec!["zero", "one", "two", "three", "four"];
let seed = ThreadRng::default().gen::<u64>();
let builder = Builder::default().seed(seed);
let map = builder.build(points, values);
let mut search = Search::default();
for (i, item) in map.search(&Point(4.0, 4.0), &mut search).enumerate() {
match i {
0 => {
assert_eq!(item.distance, 0.0);
assert_eq!(item.value, &"four");
}
1 => {
assert_eq!(item.distance, 1.4142135);
assert!(item.value == &"three");
}
2 => {
assert_eq!(item.distance, 2.828427);
assert!(item.value == &"two");
}
3 => {
assert_eq!(item.distance, 4.2426405);
assert!(item.value == &"one");
}
4 => {
assert_eq!(item.distance, 5.656854);
assert!(item.value == &"zero");
}
_ => unreachable!(),
}
}
}
}