1use crate::prelude::*;
2
3use atomic_float::AtomicF32;
4use log::info;
5use rayon::prelude::*;
6
7use std::{
8 sync::atomic::{AtomicUsize, Ordering},
9 time::Instant,
10};
11
12const INF: f32 = f32::MAX;
13const NO_BIN: usize = usize::MAX;
14const BIN_SIZE_THRESHOLD: usize = 1000;
15
16const BATCH_SIZE: usize = 64;
17
18#[derive(Copy, Clone, Debug)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20#[cfg_attr(feature = "clap", derive(clap::Args))]
21pub struct DeltaSteppingConfig {
22 #[cfg_attr(feature = "clap", clap(long))]
24 pub start_node: usize,
25
26 #[cfg_attr(feature = "clap", clap(long))]
29 pub delta: f32,
30}
31
32impl DeltaSteppingConfig {
33 pub fn new(start_node: usize, delta: f32) -> Self {
34 Self { start_node, delta }
35 }
36}
37
38pub fn delta_stepping<NI, G>(graph: &G, config: DeltaSteppingConfig) -> Vec<AtomicF32>
39where
40 NI: Idx,
41 G: Graph<NI> + DirectedNeighborsWithValues<NI, f32> + Sync,
42{
43 let start = Instant::now();
44
45 let DeltaSteppingConfig { start_node, delta } = config;
46
47 let node_count = graph.node_count().index();
48 let thread_count = rayon::current_num_threads();
49
50 let mut distance: Vec<AtomicF32> = Vec::with_capacity(node_count);
51 distance.resize_with(node_count, || AtomicF32::new(INF));
52 distance[start_node.index()].store(0.0, Ordering::Release);
53
54 let mut frontier = vec![NI::zero(); graph.edge_count().index()];
55 frontier[0] = NI::new(start_node);
56 let frontier_idx = AtomicUsize::new(0);
57 let mut frontier_len = 1;
58
59 let mut local_bins = Vec::with_capacity(thread_count);
60 local_bins.resize_with(thread_count, ThreadLocalBins::<NI>::new);
61
62 let mut curr_bin = 0;
63
64 while curr_bin != NO_BIN {
65 frontier_idx.store(0, Ordering::Relaxed);
66
67 let next_bin = local_bins
68 .par_iter_mut()
69 .map(|local_bins| {
70 process_shared_bin(
71 local_bins,
72 curr_bin,
73 graph,
74 (&frontier, &frontier_idx, frontier_len),
75 &distance,
76 delta,
77 )
78 })
79 .map(|local_bins| process_local_bins(local_bins, curr_bin, graph, &distance, delta))
80 .map(|local_bins| min_non_empty_bin(local_bins, curr_bin))
81 .min_by(|x, y| x.cmp(y))
82 .unwrap_or(NO_BIN);
83
84 frontier_len = frontier_slices(&mut frontier, &local_bins, next_bin)
86 .par_iter_mut()
87 .zip(local_bins.par_iter_mut())
88 .filter(|(_, local_bins)| local_bins.contains(next_bin))
89 .map(|(slice, local_bins)| {
90 slice.copy_from_slice(local_bins.slice(next_bin));
91 local_bins.clear(next_bin);
92 slice.len()
93 })
94 .sum();
95
96 curr_bin = next_bin;
97 }
98
99 info!("Computed SSSP in {:?}", start.elapsed());
100
101 distance
102}
103
104fn process_shared_bin<'bins, NI, G>(
105 bins: &'bins mut ThreadLocalBins<NI>,
106 curr_bin: usize,
107 graph: &G,
108 (frontier, frontier_idx, frontier_len): (&[NI], &AtomicUsize, usize),
109 distance: &[AtomicF32],
110 delta: f32,
111) -> &'bins mut ThreadLocalBins<NI>
112where
113 NI: Idx,
114 G: Graph<NI> + DirectedNeighborsWithValues<NI, f32> + Sync,
115{
116 loop {
117 let offset = frontier_idx.fetch_add(BATCH_SIZE, Ordering::AcqRel);
118
119 if offset >= frontier_len {
120 break;
121 }
122
123 let limit = usize::min(offset + BATCH_SIZE, frontier_len);
124
125 for node in frontier[offset..limit].iter() {
126 if distance[node.index()].load(Ordering::Acquire) >= delta * curr_bin as f32 {
127 relax_edges(graph, distance, bins, *node, delta);
128 }
129 }
130 }
131 bins
132}
133
134fn process_local_bins<'bins, NI, G>(
135 bins: &'bins mut ThreadLocalBins<NI>,
136 curr_bin: usize,
137 graph: &G,
138 distance: &[AtomicF32],
139 delta: f32,
140) -> &'bins mut ThreadLocalBins<NI>
141where
142 NI: Idx,
143 G: Graph<NI> + DirectedNeighborsWithValues<NI, f32> + Sync,
144{
145 while curr_bin < bins.len()
146 && !bins.is_empty(curr_bin)
147 && bins.bin_len(curr_bin) < BIN_SIZE_THRESHOLD
148 {
149 let current_bin_copy = bins.clone(curr_bin);
150 bins.clear(curr_bin);
151
152 for node in current_bin_copy {
153 relax_edges(graph, distance, bins, node, delta);
154 }
155 }
156 bins
157}
158
159fn min_non_empty_bin<NI: Idx>(local_bins: &mut ThreadLocalBins<NI>, curr_bin: usize) -> usize {
160 let mut next_local_bin = NO_BIN;
161 for bin in curr_bin..local_bins.len() {
162 if !local_bins.is_empty(bin) {
163 next_local_bin = bin;
164 break;
165 }
166 }
167 next_local_bin
168}
169
170fn relax_edges<NI, G>(
171 graph: &G,
172 distances: &[AtomicF32],
173 local_bins: &mut ThreadLocalBins<NI>,
174 node: NI,
175 delta: f32,
176) where
177 NI: Idx,
178 G: Graph<NI> + DirectedNeighborsWithValues<NI, f32> + Sync,
179{
180 for Target { target, value } in graph.out_neighbors_with_values(node) {
181 let mut old_distance = distances[target.index()].load(Ordering::Acquire);
182 let new_distance = distances[node.index()].load(Ordering::Acquire) + value;
183
184 while new_distance < old_distance {
185 match distances[target.index()].compare_exchange_weak(
186 old_distance,
187 new_distance,
188 Ordering::Release,
189 Ordering::Relaxed,
190 ) {
191 Ok(_) => {
192 let dest_bin = (new_distance / delta) as usize;
193 if dest_bin >= local_bins.len() {
194 local_bins.resize(dest_bin + 1);
195 }
196 local_bins.push(dest_bin, *target);
197 break;
198 }
199 Err(min_distance) => old_distance = min_distance,
201 }
202 }
203 }
204}
205
206fn frontier_slices<'a, NI: Idx>(
207 frontier: &'a mut [NI],
208 bins: &[ThreadLocalBins<NI>],
209 next_bin: usize,
210) -> Vec<&'a mut [NI]> {
211 let mut slices = Vec::with_capacity(bins.len());
212 let mut tail = frontier;
213
214 for local_bins in bins.iter() {
215 if local_bins.contains(next_bin) {
216 let (head, remainder) = tail.split_at_mut(local_bins.bin_len(next_bin));
217 slices.push(head);
218 tail = remainder;
219 } else {
220 slices.push(&mut []);
221 }
222 }
223
224 slices
225}
226
227#[derive(Debug)]
228struct ThreadLocalBins<T> {
229 bins: Vec<Vec<T>>,
230}
231
232impl<T> ThreadLocalBins<T>
233where
234 T: Clone,
235{
236 fn new() -> Self {
237 Self { bins: vec![vec![]] }
238 }
239
240 fn contains(&self, bin: usize) -> bool {
241 self.len() > bin
242 }
243
244 fn len(&self) -> usize {
245 self.bins.len()
246 }
247
248 fn bin_len(&self, bin: usize) -> usize {
249 self.bins[bin].len()
250 }
251
252 fn is_empty(&self, bin: usize) -> bool {
253 self.bins[bin].is_empty()
254 }
255
256 fn clone(&self, bin: usize) -> Vec<T> {
257 self.bins[bin].clone()
258 }
259
260 fn clear(&mut self, bin: usize) {
261 self.bins[bin].clear();
262 }
263
264 fn slice(&self, bin: usize) -> &[T] {
265 &self.bins[bin]
266 }
267
268 fn resize(&mut self, new_len: usize) {
269 self.bins.resize_with(new_len, Vec::new)
270 }
271
272 fn push(&mut self, bin: usize, val: T) {
273 self.bins[bin].push(val);
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280 use crate::prelude::{CsrLayout, GraphBuilder};
281
282 #[test]
283 fn test_sssp() {
284 let gdl = "(a:A)
285 (b:B)
286 (c:C)
287 (d:D)
288 (e:E)
289 (f:F)
290 (a)-[{cost: 4.0 }]->(b)
291 (a)-[{cost: 2.0 }]->(c)
292 (b)-[{cost: 5.0 }]->(c)
293 (b)-[{cost: 10.0 }]->(d)
294 (c)-[{cost: 3.0 }]->(e)
295 (d)-[{cost: 11.0 }]->(f)
296 (e)-[{cost: 4.0 }]->(d)";
297
298 let graph: DirectedCsrGraph<usize, (), f32> = GraphBuilder::new()
299 .csr_layout(CsrLayout::Deduplicated)
300 .gdl_str::<usize, _>(gdl)
301 .build()
302 .unwrap();
303
304 let config = DeltaSteppingConfig::new(0, 3.0);
305
306 let actual: Vec<f32> = delta_stepping(&graph, config)
307 .into_iter()
308 .map(|d| d.load(Ordering::Relaxed))
309 .collect();
310 let expected: Vec<f32> = vec![0.0, 4.0, 2.0, 9.0, 5.0, 20.0];
311
312 assert_eq!(actual, expected);
313 }
314}