ringkernel_graph/algorithms/
bfs.rs1use crate::models::{CsrMatrix, Distance, NodeId};
7use crate::{GraphError, Result};
8
9#[derive(Debug, Clone)]
11pub struct BfsConfig {
12 pub max_distance: u32,
14 pub bidirectional: bool,
16}
17
18impl Default for BfsConfig {
19 fn default() -> Self {
20 Self {
21 max_distance: u32::MAX - 1,
22 bidirectional: false,
23 }
24 }
25}
26
27impl BfsConfig {
28 pub fn new() -> Self {
30 Self::default()
31 }
32
33 pub fn with_max_distance(mut self, max: u32) -> Self {
35 self.max_distance = max;
36 self
37 }
38}
39
40pub fn bfs_sequential(adj: &CsrMatrix, sources: &[NodeId]) -> Result<Vec<Distance>> {
53 bfs_sequential_with_config(adj, sources, &BfsConfig::default())
54}
55
56pub fn bfs_sequential_with_config(
58 adj: &CsrMatrix,
59 sources: &[NodeId],
60 config: &BfsConfig,
61) -> Result<Vec<Distance>> {
62 if adj.num_rows == 0 {
63 return Err(GraphError::EmptyGraph);
64 }
65
66 let mut distances = vec![Distance::INFINITY; adj.num_rows];
67 let mut queue = std::collections::VecDeque::new();
68
69 for &src in sources {
71 if src.0 as usize >= adj.num_rows {
72 return Err(GraphError::InvalidNodeId(src.0 as u64));
73 }
74 if distances[src.0 as usize] == Distance::INFINITY {
75 distances[src.0 as usize] = Distance::ZERO;
76 queue.push_back(src);
77 }
78 }
79
80 while let Some(node) = queue.pop_front() {
82 let current_dist = distances[node.0 as usize];
83
84 if current_dist.0 >= config.max_distance {
85 continue;
86 }
87
88 for &neighbor_id in adj.neighbors(node) {
89 let neighbor = neighbor_id as usize;
90 if distances[neighbor] == Distance::INFINITY {
91 distances[neighbor] = current_dist.increment();
92 queue.push_back(NodeId(neighbor_id));
93 }
94 }
95 }
96
97 Ok(distances)
98}
99
100pub fn bfs_parallel(adj: &CsrMatrix, sources: &[NodeId]) -> Result<Vec<Distance>> {
110 bfs_parallel_with_config(adj, sources, &BfsConfig::default())
111}
112
113pub fn bfs_parallel_with_config(
115 adj: &CsrMatrix,
116 sources: &[NodeId],
117 config: &BfsConfig,
118) -> Result<Vec<Distance>> {
119 if adj.num_rows == 0 {
120 return Err(GraphError::EmptyGraph);
121 }
122
123 let mut distances = vec![Distance::INFINITY; adj.num_rows];
124
125 let mut frontier: Vec<NodeId> = Vec::new();
127 for &src in sources {
128 if src.0 as usize >= adj.num_rows {
129 return Err(GraphError::InvalidNodeId(src.0 as u64));
130 }
131 if distances[src.0 as usize] == Distance::INFINITY {
132 distances[src.0 as usize] = Distance::ZERO;
133 frontier.push(src);
134 }
135 }
136
137 let mut level = 0u32;
138
139 while !frontier.is_empty() && level < config.max_distance {
141 let mut next_frontier = Vec::new();
142 level += 1;
143
144 for &node in &frontier {
146 for &neighbor_id in adj.neighbors(node) {
147 let neighbor = neighbor_id as usize;
148 if distances[neighbor] == Distance::INFINITY {
150 distances[neighbor] = Distance::new(level);
151 next_frontier.push(NodeId(neighbor_id));
152 }
153 }
154 }
155
156 frontier = next_frontier;
157 }
158
159 Ok(distances)
160}
161
162pub fn bfs_with_parents(
164 adj: &CsrMatrix,
165 sources: &[NodeId],
166) -> Result<(Vec<Distance>, Vec<NodeId>)> {
167 if adj.num_rows == 0 {
168 return Err(GraphError::EmptyGraph);
169 }
170
171 let mut distances = vec![Distance::INFINITY; adj.num_rows];
172 let mut parents = vec![NodeId::INVALID; adj.num_rows];
173 let mut queue = std::collections::VecDeque::new();
174
175 for &src in sources {
177 if src.0 as usize >= adj.num_rows {
178 return Err(GraphError::InvalidNodeId(src.0 as u64));
179 }
180 if distances[src.0 as usize] == Distance::INFINITY {
181 distances[src.0 as usize] = Distance::ZERO;
182 parents[src.0 as usize] = src;
183 queue.push_back(src);
184 }
185 }
186
187 while let Some(node) = queue.pop_front() {
189 let current_dist = distances[node.0 as usize];
190
191 for &neighbor_id in adj.neighbors(node) {
192 let neighbor = neighbor_id as usize;
193 if distances[neighbor] == Distance::INFINITY {
194 distances[neighbor] = current_dist.increment();
195 parents[neighbor] = node;
196 queue.push_back(NodeId(neighbor_id));
197 }
198 }
199 }
200
201 Ok((distances, parents))
202}
203
204pub fn reconstruct_path(parents: &[NodeId], target: NodeId) -> Option<Vec<NodeId>> {
206 let target_idx = target.0 as usize;
207 if target_idx >= parents.len() || !parents[target_idx].is_valid() {
208 return None;
209 }
210
211 let mut path = vec![target];
212 let mut current = target;
213
214 while parents[current.0 as usize] != current {
216 current = parents[current.0 as usize];
217 if !current.is_valid() {
218 return None;
219 }
220 path.push(current);
221 }
222
223 path.reverse();
224 Some(path)
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 fn make_line_graph(n: usize) -> CsrMatrix {
232 let edges: Vec<_> = (0..n - 1).map(|i| (i as u32, i as u32 + 1)).collect();
234 CsrMatrix::from_edges(n, &edges)
235 }
236
237 fn make_star_graph(n: usize) -> CsrMatrix {
238 let edges: Vec<_> = (1..n).map(|i| (0, i as u32)).collect();
240 CsrMatrix::from_edges(n, &edges)
241 }
242
243 #[test]
244 fn test_bfs_line_graph() {
245 let adj = make_line_graph(5);
246 let distances = bfs_sequential(&adj, &[NodeId(0)]).unwrap();
247
248 assert_eq!(distances[0], Distance::new(0));
249 assert_eq!(distances[1], Distance::new(1));
250 assert_eq!(distances[2], Distance::new(2));
251 assert_eq!(distances[3], Distance::new(3));
252 assert_eq!(distances[4], Distance::new(4));
253 }
254
255 #[test]
256 fn test_bfs_star_graph() {
257 let adj = make_star_graph(5);
258 let distances = bfs_sequential(&adj, &[NodeId(0)]).unwrap();
259
260 assert_eq!(distances[0], Distance::new(0));
261 for d in distances.iter().take(5).skip(1) {
262 assert_eq!(*d, Distance::new(1));
263 }
264 }
265
266 #[test]
267 fn test_bfs_multi_source() {
268 let adj = make_line_graph(5);
270 let distances = bfs_sequential(&adj, &[NodeId(0), NodeId(4)]).unwrap();
271
272 assert_eq!(distances[4], Distance::new(0));
274 assert_eq!(distances[0], Distance::new(0));
276 }
277
278 #[test]
279 fn test_bfs_unreachable() {
280 let edges = [(0, 1), (2, 3)];
282 let adj = CsrMatrix::from_edges(4, &edges);
283
284 let distances = bfs_sequential(&adj, &[NodeId(0)]).unwrap();
285
286 assert_eq!(distances[0], Distance::new(0));
287 assert_eq!(distances[1], Distance::new(1));
288 assert_eq!(distances[2], Distance::INFINITY);
289 assert_eq!(distances[3], Distance::INFINITY);
290 }
291
292 #[test]
293 fn test_bfs_parallel_same_as_sequential() {
294 let adj = make_line_graph(10);
295
296 let seq = bfs_sequential(&adj, &[NodeId(0)]).unwrap();
297 let par = bfs_parallel(&adj, &[NodeId(0)]).unwrap();
298
299 assert_eq!(seq, par);
300 }
301
302 #[test]
303 fn test_bfs_max_distance() {
304 let adj = make_line_graph(10);
305 let config = BfsConfig::new().with_max_distance(3);
306 let distances = bfs_sequential_with_config(&adj, &[NodeId(0)], &config).unwrap();
307
308 assert_eq!(distances[0], Distance::new(0));
309 assert_eq!(distances[1], Distance::new(1));
310 assert_eq!(distances[2], Distance::new(2));
311 assert_eq!(distances[3], Distance::new(3));
312 assert_eq!(distances[4], Distance::INFINITY);
314 }
315
316 #[test]
317 fn test_bfs_with_parents() {
318 let adj = make_line_graph(5);
319 let (distances, parents) = bfs_with_parents(&adj, &[NodeId(0)]).unwrap();
320
321 assert_eq!(distances[4], Distance::new(4));
322
323 let path = reconstruct_path(&parents, NodeId(4)).unwrap();
325 assert_eq!(
326 path,
327 vec![NodeId(0), NodeId(1), NodeId(2), NodeId(3), NodeId(4)]
328 );
329 }
330
331 #[test]
332 fn test_empty_graph_error() {
333 let adj = CsrMatrix::empty(0);
334 let result = bfs_sequential(&adj, &[NodeId(0)]);
335 assert!(matches!(result, Err(GraphError::EmptyGraph)));
336 }
337
338 #[test]
339 fn test_invalid_source_error() {
340 let adj = make_line_graph(3);
341 let result = bfs_sequential(&adj, &[NodeId(100)]);
342 assert!(matches!(result, Err(GraphError::InvalidNodeId(_))));
343 }
344}