spade/delaunay_core/
hint_generator.rs

1use core::sync::atomic::{AtomicUsize, Ordering};
2
3use crate::{
4    DelaunayTriangulation, HasPosition, Point2, SpadeNum, Triangulation, TriangulationExt,
5};
6
7use super::FixedVertexHandle;
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12use alloc::vec::Vec;
13
14/// A structure used to speed up common operations on delaunay triangulations like insertion and geometry queries by providing
15/// hints on where to start searching for elements.
16///
17/// Without a hint, these operations run in `O(sqrt(n))` for `n` uniformly distributed vertices. Most time is spent by
18/// "walking" to the queried site (e.g. the face that is being inserted to), starting at a random vertex. A hint generator can
19/// speed this up by either using heuristics or a spatial data structure to determine where to start walking closer to the target
20/// site.
21///
22/// Hints can also be given manually by using the `...with_hint` methods (e.g.
23/// [Triangulation::insert_with_hint])
24///
25/// Usually, you should not need to implement this trait. Spade currently implements two common hint generators that should
26/// fulfill most needs:
27///  - A heuristic that uses the last inserted vertex as hint ([LastUsedVertexHintGenerator])
28///  - A hint generator based on a hierarchy of triangulations that improves walk time to `O(log(n))`
29///    ([HierarchyHintGenerator])
30pub trait HintGenerator<S: SpadeNum>: Default {
31    /// Returns a vertex handle that should be close to a given position.
32    ///
33    /// The returned vertex handle may be invalid.
34    fn get_hint(&self, position: Point2<S>) -> FixedVertexHandle;
35
36    /// Notifies the hint generator that an element was looked up
37    fn notify_vertex_lookup(&self, vertex: FixedVertexHandle);
38    /// Notifies the hint generator that a new vertex is inserted
39    fn notify_vertex_inserted(&mut self, vertex: FixedVertexHandle, vertex_position: Point2<S>);
40    /// Notifies the hint generator that a vertex was removed
41    fn notify_vertex_removed(
42        &mut self,
43        swapped_in_point: Option<Point2<S>>,
44        vertex: FixedVertexHandle,
45        vertex_position: Point2<S>,
46    );
47
48    /// Creates a new hint generator initialized to give hints for a specific triangulation
49    fn initialize_from_triangulation<TR, V>(triangulation: &TR) -> Self
50    where
51        TR: Triangulation<Vertex = V>,
52        V: HasPosition<Scalar = S>;
53}
54
55/// A hint generator that returns the last used vertex as hint.
56///
57/// This is useful if multiple insertion or locate queries are spatially close instead of randomly distributed.
58/// The run time of insertion and locate queries will be bounded by a constant in this case.
59///
60/// This heuristic requires only a constant additional amount of memory.
61///
62/// # Example
63/// ```
64/// use spade::{DelaunayTriangulation, LastUsedVertexHintGenerator, Point2, Triangulation};
65///
66/// type LastUsedVertexTriangulation =
67///     DelaunayTriangulation<Point2<f64>, (), (), (), LastUsedVertexHintGenerator>;
68///
69/// let mut triangulation = LastUsedVertexTriangulation::new();
70/// // Start using the triangulation, e.g. by inserting vertices
71/// triangulation.insert(Point2::new(0.0, 0.0));
72/// ```
73#[derive(Default, Debug)]
74#[cfg_attr(
75    feature = "serde",
76    derive(Serialize, Deserialize),
77    serde(crate = "serde")
78)]
79pub struct LastUsedVertexHintGenerator {
80    // Serde does not implement `(De)Serialize` for `AtomicUsize` in no_std environments.
81    #[cfg_attr(feature = "serde", serde(skip))]
82    index: AtomicUsize,
83}
84
85impl Clone for LastUsedVertexHintGenerator {
86    fn clone(&self) -> Self {
87        Self {
88            index: AtomicUsize::new(self.index.load(Ordering::Relaxed)),
89        }
90    }
91}
92
93impl<S: SpadeNum> HintGenerator<S> for LastUsedVertexHintGenerator {
94    fn get_hint(&self, _: Point2<S>) -> FixedVertexHandle {
95        FixedVertexHandle::new(self.index.load(Ordering::Relaxed))
96    }
97
98    fn notify_vertex_lookup(&self, vertex: FixedVertexHandle) {
99        self.index.store(vertex.index(), Ordering::Relaxed);
100    }
101
102    fn notify_vertex_inserted(&mut self, vertex: FixedVertexHandle, _: Point2<S>) {
103        <Self as HintGenerator<S>>::notify_vertex_lookup(self, vertex);
104    }
105
106    fn notify_vertex_removed(
107        &mut self,
108        _swapped_in_point: Option<Point2<S>>,
109        vertex: FixedVertexHandle,
110        _vertex_position: Point2<S>,
111    ) {
112        // Use the previous vertex handle as next hint. This should be a good hint if vertices
113        // were inserted in local batches.
114        let hint = FixedVertexHandle::new(vertex.index().saturating_sub(1));
115        <Self as HintGenerator<S>>::notify_vertex_lookup(self, hint);
116    }
117
118    fn initialize_from_triangulation<TR, V>(_: &TR) -> Self
119    where
120        TR: Triangulation,
121        V: HasPosition<Scalar = S>,
122    {
123        Self::default()
124    }
125}
126
127/// A hint generator based on a hierarchy of triangulations optimized for randomly accessing elements of
128/// the triangulation.
129///
130/// Using this hint generator results in an insertion and lookup performance of O(log(n)) by keeping a
131/// few layers of sparsely populated Delaunay Triangulations. These layers can then be quickly traversed
132/// before diving deeper into the next, more detailed layer where the search is furthermore refined.
133///
134/// # Type parameters
135///  - `S`: The scalar type used by the triangulation
136///
137/// # Example
138/// ```
139/// use spade::{Point2, Triangulation, DelaunayTriangulation, HierarchyHintGenerator};
140///
141/// pub type HierarchyTriangulation = DelaunayTriangulation<Point2<f64>, (), (), (), HierarchyHintGenerator<f64>>;
142///
143/// let mut triangulation = HierarchyTriangulation::new();
144/// // Start using the triangulation, e.g. by inserting vertices
145/// triangulation.insert(Point2::new(0.0, 0.0));
146/// ```
147pub type HierarchyHintGenerator<S> = HierarchyHintGeneratorWithBranchFactor<S, 16>;
148
149#[derive(Debug, Clone)]
150#[cfg_attr(
151    feature = "serde",
152    derive(Serialize, Deserialize),
153    serde(crate = "serde")
154)]
155#[doc(hidden)]
156pub struct HierarchyHintGeneratorWithBranchFactor<S: SpadeNum, const BRANCH_FACTOR: u32> {
157    hierarchy: Vec<DelaunayTriangulation<Point2<S>>>,
158    num_elements_of_base_triangulation: usize,
159}
160
161impl<S: SpadeNum, const BRANCH_FACTOR: u32> Default
162    for HierarchyHintGeneratorWithBranchFactor<S, BRANCH_FACTOR>
163{
164    fn default() -> Self {
165        Self {
166            hierarchy: Vec::new(),
167            num_elements_of_base_triangulation: 0,
168        }
169    }
170}
171
172impl<S: SpadeNum, const BRANCH_FACTOR: u32> HintGenerator<S>
173    for HierarchyHintGeneratorWithBranchFactor<S, BRANCH_FACTOR>
174{
175    fn get_hint(&self, position: Point2<S>) -> FixedVertexHandle {
176        let mut nearest = FixedVertexHandle::new(0);
177        for layer in self.hierarchy.iter().rev().skip(1) {
178            nearest = layer.walk_to_nearest_neighbor(nearest, position).fix();
179            let hint_generator: &LastUsedVertexHintGenerator = layer.hint_generator();
180            <LastUsedVertexHintGenerator as HintGenerator<S>>::notify_vertex_lookup(
181                hint_generator,
182                nearest,
183            );
184            nearest = FixedVertexHandle::new(nearest.index() * BRANCH_FACTOR as usize);
185        }
186        nearest
187    }
188
189    fn notify_vertex_lookup(&self, _: FixedVertexHandle) {}
190
191    fn notify_vertex_inserted(&mut self, vertex: FixedVertexHandle, vertex_position: Point2<S>) {
192        self.num_elements_of_base_triangulation += 1;
193
194        // Find first layer to insert into. Insert into all higher layers.
195        let mut index = vertex.index() as u32;
196
197        let mut remainder = 0;
198        for triangulation in &mut self.hierarchy {
199            remainder = index % BRANCH_FACTOR;
200            index /= BRANCH_FACTOR;
201
202            if remainder == 0 {
203                triangulation.insert(vertex_position).unwrap();
204            } else {
205                break;
206            }
207        }
208
209        if remainder == 0 {
210            let mut new_layer = DelaunayTriangulation::new();
211            let position_of_vertex_0 = self
212                .hierarchy
213                .first()
214                .map(|layer| layer.vertex(FixedVertexHandle::new(0)).position())
215                .unwrap_or(vertex_position);
216            new_layer.insert(position_of_vertex_0).unwrap();
217            self.hierarchy.push(new_layer);
218        }
219    }
220
221    fn notify_vertex_removed(
222        &mut self,
223        mut swapped_in_point: Option<Point2<S>>,
224        vertex: FixedVertexHandle,
225        _vertex_position: Point2<S>,
226    ) {
227        if self.num_elements_of_base_triangulation == 1 {
228            // Special case: Triangulation has become empty
229            *self = Default::default();
230            return;
231        }
232
233        let index = vertex.index() as u32;
234
235        let mut current_divisor = BRANCH_FACTOR;
236        self.num_elements_of_base_triangulation -= 1;
237        let mut last_layer_size = self.num_elements_of_base_triangulation;
238
239        for triangulation in &mut self.hierarchy {
240            let remainder = index % current_divisor;
241            let index_to_remove = index / current_divisor;
242            current_divisor *= BRANCH_FACTOR;
243
244            if remainder == 0 {
245                // The current handle is part of this layer and must be removed.
246                if let Some(swapped_point) = swapped_in_point.as_ref() {
247                    if (triangulation.num_vertices() - 1) * (BRANCH_FACTOR as usize)
248                        != last_layer_size
249                    {
250                        // Only insert a new element if the swapped element is not already present
251                        // in the layer
252                        triangulation.insert(*swapped_point).unwrap();
253                    }
254                }
255                triangulation.remove(FixedVertexHandle::new(index_to_remove as usize));
256            }
257
258            let prev_num_vertices = last_layer_size as u32;
259            let max_num_vertices = prev_num_vertices.div_ceil(BRANCH_FACTOR);
260            if triangulation.num_vertices() as u32 > max_num_vertices {
261                // The layer contains too many elements. Remove the last.
262                let vertex_to_pop = FixedVertexHandle::new(triangulation.num_vertices() - 1);
263                swapped_in_point = None;
264                triangulation.remove(vertex_to_pop);
265            }
266
267            last_layer_size = triangulation.num_vertices();
268        }
269
270        if let [.., ref before_last, _] = self.hierarchy.as_slice() {
271            if before_last.num_vertices() == 1 {
272                // Last layer has become irrelevant
273                self.hierarchy.pop();
274            }
275        }
276    }
277
278    fn initialize_from_triangulation<TR, V>(triangulation: &TR) -> Self
279    where
280        TR: Triangulation<Vertex = V>,
281        V: HasPosition<Scalar = S>,
282    {
283        let mut result = Self::default();
284
285        if triangulation.num_vertices() == 0 {
286            return result;
287        }
288
289        let mut current_step = BRANCH_FACTOR as usize;
290        loop {
291            let vertices = triangulation
292                .vertices()
293                .map(|v| v.position())
294                .step_by(current_step)
295                .collect::<Vec<_>>();
296
297            result.hierarchy.push(
298                DelaunayTriangulation::bulk_load_stable(vertices)
299                    .expect("Failed to load hierarchy layer."),
300            );
301
302            if current_step >= triangulation.num_vertices() {
303                break;
304            }
305
306            current_step *= BRANCH_FACTOR as usize;
307        }
308
309        result.num_elements_of_base_triangulation = triangulation.num_vertices();
310        result
311    }
312}
313
314#[cfg(test)]
315mod test {
316    use rand::{seq::IndexedRandom as _, RngCore, SeedableRng};
317
318    use crate::{
319        handles::FixedVertexHandle, test_utilities, DelaunayTriangulation, InsertionError, Point2,
320        Triangulation, TriangulationExt,
321    };
322
323    use alloc::vec::Vec;
324
325    const BRANCH_FACTOR: u32 = 3;
326
327    type HierarchyTriangulation = DelaunayTriangulation<
328        Point2<f64>,
329        (),
330        (),
331        (),
332        super::HierarchyHintGeneratorWithBranchFactor<f64, BRANCH_FACTOR>,
333    >;
334
335    #[test]
336    fn hierarchy_hint_generator_test() -> Result<(), InsertionError> {
337        let vertices = test_utilities::random_points_with_seed(1025, test_utilities::SEED);
338        let triangulation = HierarchyTriangulation::bulk_load(vertices.clone())?;
339
340        hierarchy_sanity_check(&triangulation);
341
342        // Test sequential load
343        let mut triangulation = HierarchyTriangulation::new();
344        for vertex in vertices {
345            triangulation.insert(vertex)?;
346        }
347        hierarchy_sanity_check(&triangulation);
348        Ok(())
349    }
350
351    fn hierarchy_sanity_check(triangulation: &HierarchyTriangulation) {
352        let expected_len = if triangulation.num_vertices() <= 1 {
353            triangulation.num_vertices()
354        } else {
355            (triangulation.num_vertices() as f64)
356                .log(BRANCH_FACTOR as f64)
357                .ceil() as usize
358        };
359
360        assert_eq!(triangulation.hint_generator.hierarchy.len(), expected_len);
361
362        for vertex in triangulation.vertices() {
363            let position = vertex.position();
364            let base_index = vertex.fix().index() as u32;
365            let mut power = BRANCH_FACTOR;
366
367            for layer in &triangulation.hint_generator().hierarchy {
368                if base_index % power == 0 {
369                    let corresponding_vertex =
370                        FixedVertexHandle::new((base_index / power) as usize);
371                    assert_eq!(layer.vertex(corresponding_vertex).position(), position);
372                    power *= BRANCH_FACTOR;
373                } else {
374                    assert!(layer.locate_vertex(position).is_none());
375                }
376            }
377        }
378    }
379
380    #[test]
381    fn test_hierarchy_hint_generator_bulk_load_small() -> Result<(), InsertionError> {
382        let mut rng = rand::rngs::StdRng::from_seed(*test_utilities::SEED);
383        let mut seed_fn = || {
384            let mut seed = [0u8; 32];
385            rng.fill_bytes(seed.as_mut());
386            seed
387        };
388
389        for size in 0..5 {
390            let vertices = test_utilities::random_points_with_seed(size, &seed_fn());
391            let triangulation = HierarchyTriangulation::bulk_load(vertices)?;
392            hierarchy_sanity_check(&triangulation);
393            triangulation.sanity_check();
394        }
395
396        for size in 1..20 {
397            let vertices = test_utilities::random_points_with_seed(1 + size * 26, &seed_fn());
398            let triangulation = HierarchyTriangulation::bulk_load_stable(vertices)?;
399            hierarchy_sanity_check(&triangulation);
400        }
401        Ok(())
402    }
403
404    #[test]
405    fn hierarchy_hint_generator_removal_test() -> Result<(), InsertionError> {
406        let vertices = test_utilities::random_points_with_seed(300, test_utilities::SEED);
407        let mut triangulation = HierarchyTriangulation::bulk_load(vertices)?;
408
409        let mut rng = rand::rngs::StdRng::from_seed(*test_utilities::SEED2);
410        while let Some(to_remove) = triangulation
411            .fixed_vertices()
412            .collect::<Vec<_>>()
413            .choose(&mut rng)
414        {
415            triangulation.remove(*to_remove);
416            hierarchy_sanity_check(&triangulation);
417        }
418        Ok(())
419    }
420}