Skip to main content

oxgraph_algo/bfs/
scratch.rs

1//! Scratch-backed no-allocation indexed BFS (forward and reverse).
2
3use core::iter::FusedIterator;
4
5use oxgraph_topology::{
6    ContainsElement, ElementId, ElementIndex, ElementPredecessors, ElementSuccessors,
7};
8
9use crate::bfs::{
10    BfsError,
11    core::{Bfs, Forward, Reverse, SeededByteFlagFrontier, ValidatedScratch},
12};
13
14/// Forward BFS iterator using caller-provided byte-flag scratch.
15///
16/// Constructed only via [`breadth_first_search_with_scratch`]. The internal
17/// storage policy ([`SeededByteFlagFrontier`]) is intentionally not part of
18/// the public API.
19///
20/// # Performance
21///
22/// For a reachable subgraph with `n` elements and `m` successor entries
23/// inspected, traversal is `O(n + m)` and uses `O(b)` caller-provided scratch
24/// memory for `b = graph.element_bound()`.
25#[must_use = "iterators are lazy and do nothing unless consumed"]
26pub struct BreadthFirstSearchScratch<'graph, 'scratch, G>
27where
28    G: ContainsElement + ElementSuccessors + ElementIndex,
29{
30    /// Underlying generic BFS driver carrying the seeded frontier.
31    inner: Bfs<'graph, G, SeededByteFlagFrontier<'scratch, G>, Forward>,
32}
33
34impl<G> Iterator for BreadthFirstSearchScratch<'_, '_, G>
35where
36    G: ContainsElement + ElementSuccessors + ElementIndex,
37{
38    type Item = ElementId<G>;
39
40    fn next(&mut self) -> Option<Self::Item> {
41        self.inner.next()
42    }
43}
44
45impl<G> FusedIterator for BreadthFirstSearchScratch<'_, '_, G> where
46    G: ContainsElement + ElementSuccessors + ElementIndex
47{
48}
49
50/// Creates a forward breadth-first traversal using caller-provided scratch.
51///
52/// Traversal follows outgoing connections and yields each reachable element at
53/// most once. The start element is yielded first. `visited` and `queue` must
54/// each have at least `graph.element_bound()` entries. The scratch slices are
55/// cleared and reused by the traversal; their previous contents are ignored.
56///
57/// `start` and all IDs returned by `graph.element_successors` must be valid
58/// for `graph` and must map to indexes below `graph.element_bound()`. An
59/// expansion-target index at or past the bound observed at construction time
60/// will panic with [`BfsError::NeighborIndexOutOfBounds`]; this is a topology
61/// contract violation, not a recoverable failure.
62///
63/// # Errors
64///
65/// Returns [`BfsError::VisitedTooSmall`] or [`BfsError::QueueTooSmall`] when
66/// the corresponding scratch slice is smaller than `graph.element_bound()`,
67/// [`BfsError::StartElementNotContained`] when `start` is not contained in
68/// `graph`, or [`BfsError::StartIndexOutOfBounds`] when `start` maps outside
69/// `graph.element_bound()`. Errors are returned in that order: scratch sizes
70/// are checked before start-element validation.
71///
72/// # Performance
73///
74/// Construction clears `O(b)` visited entries for `b = graph.element_bound()`
75/// and performs no heap allocation. Traversal over a reachable subgraph with
76/// `n` elements and `m` successor entries inspected is `O(n + m)` and performs
77/// no heap allocation. Reusing scratch across traversals reuses the same
78/// memory.
79pub fn breadth_first_search_with_scratch<'graph, 'scratch, G>(
80    graph: &'graph G,
81    start: ElementId<G>,
82    visited: &'scratch mut [u8],
83    queue: &'scratch mut [ElementId<G>],
84) -> Result<BreadthFirstSearchScratch<'graph, 'scratch, G>, BfsError>
85where
86    G: ContainsElement + ElementSuccessors + ElementIndex,
87{
88    let witness = ValidatedScratch::new(graph, start, visited.len(), queue.len())?;
89    let frontier = SeededByteFlagFrontier::new(visited, queue, start, witness);
90    Ok(BreadthFirstSearchScratch {
91        inner: Bfs::new(graph, frontier),
92    })
93}
94
95/// Reverse BFS iterator using caller-provided byte-flag scratch.
96///
97/// Constructed only via [`reverse_breadth_first_search_with_scratch`]. The
98/// internal storage policy ([`SeededByteFlagFrontier`]) is intentionally not
99/// part of the public API.
100///
101/// # Performance
102///
103/// For a reverse-reachable subgraph with `n` elements and `m` predecessor
104/// entries inspected, traversal is `O(n + m)` and uses `O(b)` caller-provided
105/// scratch memory for `b = graph.element_bound()`.
106#[must_use = "iterators are lazy and do nothing unless consumed"]
107pub struct ReverseBreadthFirstSearchScratch<'graph, 'scratch, G>
108where
109    G: ContainsElement + ElementPredecessors + ElementIndex,
110{
111    /// Underlying generic BFS driver carrying the seeded frontier and reverse
112    /// direction selector.
113    inner: Bfs<'graph, G, SeededByteFlagFrontier<'scratch, G>, Reverse>,
114}
115
116impl<G> Iterator for ReverseBreadthFirstSearchScratch<'_, '_, G>
117where
118    G: ContainsElement + ElementPredecessors + ElementIndex,
119{
120    type Item = ElementId<G>;
121
122    fn next(&mut self) -> Option<Self::Item> {
123        self.inner.next()
124    }
125}
126
127impl<G> FusedIterator for ReverseBreadthFirstSearchScratch<'_, '_, G> where
128    G: ContainsElement + ElementPredecessors + ElementIndex
129{
130}
131
132/// Creates a reverse breadth-first traversal using caller-provided scratch.
133///
134/// Traversal follows incoming connections and yields each reverse-reachable
135/// element at most once. The start element is yielded first. `visited` and
136/// `queue` must each have at least `graph.element_bound()` entries. The
137/// scratch slices are cleared and reused by the traversal; their previous
138/// contents are ignored.
139///
140/// `start` and all IDs returned by `graph.element_predecessors` must be valid
141/// for `graph` and must map to indexes below `graph.element_bound()`. An
142/// expansion-target index at or past the bound observed at construction time
143/// will panic with [`BfsError::NeighborIndexOutOfBounds`]; this is a topology
144/// contract violation, not a recoverable failure.
145///
146/// # Errors
147///
148/// Returns [`BfsError::VisitedTooSmall`] or [`BfsError::QueueTooSmall`] when
149/// the corresponding scratch slice is smaller than `graph.element_bound()`,
150/// [`BfsError::StartElementNotContained`] when `start` is not contained in
151/// `graph`, or [`BfsError::StartIndexOutOfBounds`] when `start` maps outside
152/// `graph.element_bound()`. Errors are returned in that order.
153///
154/// # Performance
155///
156/// Same shape as [`breadth_first_search_with_scratch`] with predecessor
157/// expansion in place of successor expansion.
158pub fn reverse_breadth_first_search_with_scratch<'graph, 'scratch, G>(
159    graph: &'graph G,
160    start: ElementId<G>,
161    visited: &'scratch mut [u8],
162    queue: &'scratch mut [ElementId<G>],
163) -> Result<ReverseBreadthFirstSearchScratch<'graph, 'scratch, G>, BfsError>
164where
165    G: ContainsElement + ElementPredecessors + ElementIndex,
166{
167    let witness = ValidatedScratch::new(graph, start, visited.len(), queue.len())?;
168    let frontier = SeededByteFlagFrontier::new(visited, queue, start, witness);
169    Ok(ReverseBreadthFirstSearchScratch {
170        inner: Bfs::new(graph, frontier),
171    })
172}